Compare commits
19 Commits
main
..
4e29c93552
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e29c93552 | |||
| f64450bdfa | |||
| e7af73992d | |||
| c42b62b473 | |||
| 27da85879f | |||
| e6357420ec | |||
| 20008f09d0 | |||
| f7e7e3eb72 | |||
| 3f9dfe5902 | |||
| 850bc8166f | |||
| 4cdd01202d | |||
| 482be9db5f | |||
| c74f5a7001 | |||
| adcceb874d | |||
| 846475326d | |||
| a22e072f82 | |||
| 7610a28042 | |||
| 0662bac5c3 | |||
| 06e1cffae7 |
@@ -0,0 +1,3 @@
|
||||
[submodule "RAIDGuardX"]
|
||||
path = RAIDGuardX
|
||||
url = https://gitea.momentry.ddns.net/warren/RAIDGuardX.git
|
||||
@@ -0,0 +1,234 @@
|
||||
# AGENTS.md - RAIDGuard X Development Guide
|
||||
|
||||
## Project Overview
|
||||
|
||||
RAIDGuard X is a Java 17 Swing desktop application for managing and monitoring RAID storage systems (by Accusys).
|
||||
|
||||
**Location:** `RAIDGuardX/`
|
||||
**Main Entry Point:** `raidguard_x.AppMain`
|
||||
**Java Version:** 17
|
||||
|
||||
---
|
||||
|
||||
## Build Commands
|
||||
|
||||
```bash
|
||||
# Navigate to project directory
|
||||
cd RAIDGuardX
|
||||
|
||||
# Compile the project
|
||||
mvn compile
|
||||
|
||||
# Run the application
|
||||
mvn compile exec:java -Dexec.mainClass="raidguard_x.AppMain"
|
||||
|
||||
# Run with log output
|
||||
mvn compile exec:java -Dexec.mainClass="raidguard_x.AppMain" 2>&1 | tee RAIDGuardX_$(date +%Y%m%d_%H%M).log
|
||||
|
||||
# Quick run (if already compiled)
|
||||
mvn exec:java -Dexec.mainClass="raidguard_x.AppMain"
|
||||
|
||||
# Full clean build with packaging (creates macOS app)
|
||||
mvn clean package
|
||||
|
||||
# Create JAR file only
|
||||
mvn jar:jar
|
||||
|
||||
# Copy dependencies to target/dependency-libs
|
||||
mvn dependency:copy-dependencies
|
||||
|
||||
# Run JAR directly
|
||||
java -cp "target/raidguard_x-1.0.562.jar:target/dependency-libs/*" raidguard_x.AppMain
|
||||
```
|
||||
|
||||
### Running Single Tests
|
||||
|
||||
This project uses **JUnit 3.8.1** for testing. Test files are in `src/test/java/`.
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
mvn test
|
||||
|
||||
# Run a specific test class
|
||||
mvn test -Dtest=TestClassName
|
||||
|
||||
# Run a specific test method
|
||||
mvn test -Dtest=TestClassName#testMethodName
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/main/java/raidguard_x/
|
||||
├── AppMain.java # Main entry point (applet/application)
|
||||
├── frmMain.java # Main application window
|
||||
├── InBandAPI/ # RAID controller communication (port 8922)
|
||||
│ ├── SendCmd.java # Command sending and protocol handling
|
||||
│ ├── BasePage.java # Base class for info pages
|
||||
│ ├── ControllerInfoPage.java
|
||||
│ ├── RAIDInfoPage.java
|
||||
│ └── ...
|
||||
├── SNMP/ # SNMP trap implementation
|
||||
│ ├── SNMPSender.java
|
||||
│ ├── SNMPTrapSenderInterface.java
|
||||
│ ├── SNMPv1TrapPDU.java
|
||||
│ └── ...
|
||||
├── Exception/ # Custom exceptions
|
||||
│ ├── AMEException.java
|
||||
│ └── ErrMsg.java
|
||||
├── frm*.java # Form/dialog classes (JFrame)
|
||||
├── dlg*.java # Dialog classes
|
||||
├── jp*.java # JPanel classes
|
||||
├── PageDataDB.java # Global data store
|
||||
├── Langs.java # Localization support
|
||||
└── Debug.java # Logging utility
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Code Style Guidelines
|
||||
|
||||
### General Conventions
|
||||
|
||||
1. **Indentation:** 4 spaces (no tabs)
|
||||
2. **Braces:** Same-line style (K&R variant)
|
||||
```java
|
||||
public class Example {
|
||||
public void method() {
|
||||
if (condition) {
|
||||
// code
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
3. **Line length:** No strict limit, but avoid excessive wrapping
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
| Element | Convention | Example |
|
||||
|---------|------------|---------|
|
||||
| Classes | PascalCase | `AppMain`, `SendCmd` |
|
||||
| Methods | camelCase | `sendPassword()`, `convetToAMEReplyFrame()` |
|
||||
| Variables | camelCase | `sIP`, `replyBuf`, `pageCode` |
|
||||
| Constants | UPPER_SNAKE | (rarely used) |
|
||||
| Packages | lowercase | `raidguard_x`, `exception` |
|
||||
|
||||
**Prefix conventions observed in codebase:**
|
||||
- `s` prefix for Strings: `sIP`, `sSN`, `sVMVer`
|
||||
- `i` prefix for integers: `iCmdLen`, `iR`
|
||||
- `b`/`byte` for byte arrays: `byte[] replyBuf`
|
||||
|
||||
### Import Organization
|
||||
|
||||
```java
|
||||
package raidguard_x;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import raidguard_x.*;
|
||||
import raidguard_x.Exception.*;
|
||||
```
|
||||
|
||||
- Standard library imports first
|
||||
- Third-party imports second
|
||||
- Project imports last
|
||||
- Use wildcard imports for related packages (`java.io.*`, `java.net.*`)
|
||||
|
||||
### Error Handling
|
||||
|
||||
- Use custom `AMEException` for protocol errors
|
||||
- Use `try-catch` blocks for I/O and network operations
|
||||
- Catch specific exceptions before generic ones
|
||||
- Always close resources in `finally` blocks
|
||||
|
||||
```java
|
||||
try {
|
||||
// network I/O code
|
||||
} catch (UnknownHostException ex) {
|
||||
System.err.println("Unknown host: " + ex.getMessage());
|
||||
} catch (IOException ex) {
|
||||
System.err.println("IO error: " + ex.getMessage());
|
||||
} catch (AMEException ex) {
|
||||
throw ex;
|
||||
} finally {
|
||||
try {
|
||||
if (socket != null) socket.close();
|
||||
} catch (Exception e) { /* ignore */ }
|
||||
}
|
||||
```
|
||||
|
||||
### Logging/Debugging
|
||||
|
||||
Use the `Debug` class for structured logging:
|
||||
|
||||
```java
|
||||
Debug.Log(">>> [Protocol] GET_PAGE: 0x%02X (%s)", pageCode, pageName);
|
||||
Debug.HexDump("RAW RECEIVED DATA", actualRead);
|
||||
```
|
||||
|
||||
Avoid `System.out.println` for debug output (existing code may still use it).
|
||||
|
||||
### Swing/GUI Conventions
|
||||
|
||||
1. **EDT Compliance:** Use `SwingUtilities.invokeLater()` for UI updates
|
||||
```java
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
// UI code
|
||||
}
|
||||
});
|
||||
```
|
||||
2. **Platform Detection:** Use `AppMain.MAC_OS_X` and `AppMain.WIN` flags
|
||||
3. **Font Settings:** Apply via `UIManager.put()` in `initGlobalFontSetting()`
|
||||
|
||||
### Comments
|
||||
|
||||
- Use Javadoc for public classes and methods
|
||||
- Inline comments for non-obvious logic
|
||||
- Chinese comments present in original code for debugging sections
|
||||
|
||||
### Type Annotations
|
||||
|
||||
- Use raw types sparingly (legacy codebase)
|
||||
- Avoid generic warnings when possible
|
||||
- Prefer `List<E>`, `Map<K,V>` over raw `ArrayList`, `HashMap`
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Library | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| JUnit | 3.8.1 | Testing |
|
||||
| javax.mail | 1.6.2 | Email functionality |
|
||||
| org.json | 20231013 | JSON parsing |
|
||||
|
||||
---
|
||||
|
||||
## Key Protocols
|
||||
|
||||
### In-Band API (Port 8922)
|
||||
|
||||
Protocol for communicating with RAID controllers:
|
||||
- OpCode 0x01: GET_PAGE (read info pages)
|
||||
- OpCode 0x1D: SEND_PASSWORD
|
||||
- OpCode 0xBC: Unlock
|
||||
- OpCode 0x1B: Erase RAID
|
||||
- OpCode 0xCC: Create RAID
|
||||
- OpCode 0xCE: Set Global Config
|
||||
- OpCode 0x26: Commit Configuration
|
||||
|
||||
### SNMP
|
||||
|
||||
Custom SNMP implementation supporting v1 and v2 traps for event notification.
|
||||
|
||||
---
|
||||
|
||||
## Development Notes
|
||||
|
||||
1. **Java 17 Required:** Set `<maven.compiler.release>17</maven.compiler.release>` in pom.xml
|
||||
2. **macOS Considerations:** Set `apple.laf.useScreenMenuBar=true` for menu bar
|
||||
3. **Socket Timeout:** Default 3 seconds (prevent hangs on network issues)
|
||||
4. **DEXT Service:** C++ service must be running on port 8922 for full functionality
|
||||
@@ -1,31 +1,6 @@
|
||||
[package]
|
||||
name = "raidguard_x_gui_client"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Accusys"]
|
||||
description = "RAIDGuard X GUI Client - Slint-based cross-platform GUI"
|
||||
|
||||
[lib]
|
||||
name = "raidguard_x_gui_client"
|
||||
crate-type = ["lib", "cdylib", "staticlib"]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
slint = "1.15"
|
||||
tokio = { version = "1.42", features = ["full"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
uuid = { version = "1.10", features = ["v4"] }
|
||||
thiserror = "2.0"
|
||||
parking_lot = "0.12"
|
||||
anyhow = "1.0"
|
||||
chrono = "0.4"
|
||||
|
||||
[[bin]]
|
||||
name = "raidguard-client"
|
||||
path = "src/main.rs"
|
||||
|
||||
[build-dependencies]
|
||||
slint-build = "1.7"
|
||||
[workspace]
|
||||
members = [
|
||||
"raidguard_x_gui_server",
|
||||
"raidguard_x_gui_client",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
# GC_3.8.0 專案分析文件
|
||||
|
||||
## 1. 專案概述
|
||||
|
||||
GC_3.8.0 是 Accusys 的 RAID 儲存管理系統解決方案,包含一個完整的桌面應用程式用於管理和監控 RAID 儲存系統。該專案採用混合技術架構,結合了傳統的 Java Swing 應用程式和現代的 Rust GUI 客戶端。
|
||||
|
||||
**專案位置**: `/Users/accusys/GC_3.8.0`
|
||||
|
||||
---
|
||||
|
||||
## 2. 專案架構
|
||||
|
||||
```
|
||||
GC_3.8.0/
|
||||
├── RAIDGuardX/ # Java 17 Swing 桌面應用程式
|
||||
├── raidguard_x_gui_client/ # Rust Slint GUI 客戶端 (新開發)
|
||||
├── raidguard_x_gui_server/ # Rust 後端伺服器
|
||||
├── AGENTS.md # Agent 開發指南
|
||||
├── README_GUI.md # GUI 架構說明
|
||||
├── Cargo.toml # Rust Workspace 配置
|
||||
├── build.sh # 編譯腳本
|
||||
└── test_client.rs # 測試客戶端
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 主要元件分析
|
||||
|
||||
### 3.1 RAIDGuardX (Java 17)
|
||||
|
||||
**技術棧**:
|
||||
- Java 17
|
||||
- Maven build system
|
||||
- Swing UI framework
|
||||
|
||||
**主要功能**:
|
||||
- RAID 控制器管理
|
||||
- RAID 陣列配置
|
||||
- 磁碟監控
|
||||
- 事件日誌
|
||||
- In-Band API 通訊 (port 8922)
|
||||
- SNMP trap 支援
|
||||
|
||||
**專案結構**:
|
||||
```
|
||||
src/main/java/raidguard_x/
|
||||
├── AppMain.java # 主入口點
|
||||
├── frmMain.java # 主應用視窗
|
||||
├── InBandAPI/ # RAID 控制器通訊
|
||||
│ ├── SendCmd.java # 命令發送與協議處理
|
||||
│ ├── BasePage.java
|
||||
│ └── ...
|
||||
├── SNMP/ # SNMP trap 實現
|
||||
├── Exception/ # 自定義異常
|
||||
├── frm*.java # 表單類別
|
||||
├── dlg*.java # 對話框類別
|
||||
└── jp*.java # JPanel 類別
|
||||
```
|
||||
|
||||
**關鍵協議** (In-Band API - Port 8922):
|
||||
| OpCode | 功能 |
|
||||
|--------|------|
|
||||
| 0x01 | GET_PAGE |
|
||||
| 0x1D | SEND_PASSWORD |
|
||||
| 0xBC | Unlock |
|
||||
| 0x1B | Erase RAID |
|
||||
| 0xCC | Create RAID |
|
||||
| 0xCE | Set Global Config |
|
||||
| 0x26 | Commit Configuration |
|
||||
|
||||
### 3.2 raidguard_x_gui_client (Rust)
|
||||
|
||||
**技術棧**:
|
||||
- Rust
|
||||
- Slint UI framework
|
||||
- TCP JSON 協議
|
||||
|
||||
**功能**:
|
||||
- 跨平台 GUI (macOS, Windows, Linux)
|
||||
- 連接後端伺服器
|
||||
- 即時資料顯示
|
||||
|
||||
**專案結構**:
|
||||
```
|
||||
raidguard_x_gui_client/
|
||||
├── Cargo.toml
|
||||
├── build.rs # Slint build script
|
||||
├── src/
|
||||
│ ├── main.rs # 入口點
|
||||
│ ├── lib.rs
|
||||
│ ├── app.rs # 狀態管理
|
||||
│ ├── models/ # 資料模型
|
||||
│ └── protocol/ # 用戶端協議
|
||||
└── ui/
|
||||
└── main_window.slint # Slint UI 定義
|
||||
```
|
||||
|
||||
### 3.3 raidguard_x_gui_server (Rust)
|
||||
|
||||
**技術棧**:
|
||||
- Rust
|
||||
- TCP 伺服器
|
||||
- JSON 協議
|
||||
- Mock 資料提供者
|
||||
|
||||
**功能**:
|
||||
- 提供 RAID 控制器資料
|
||||
- 目前提供 Mock 資料
|
||||
- 未來計劃: 連接真實 RAID 系統
|
||||
|
||||
**專案結構**:
|
||||
```
|
||||
raidguard_x_gui_server/
|
||||
├── Cargo.toml
|
||||
└── src/
|
||||
├── main.rs
|
||||
├── lib.rs
|
||||
├── server.rs # TCP 伺服器
|
||||
├── protocol/ # JSON 消息協議
|
||||
├── handlers/ # 請求處理
|
||||
└── mock/ # Mock 資料
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 通訊協議
|
||||
|
||||
### 請求格式
|
||||
```json
|
||||
{
|
||||
"id": "uuid-v4",
|
||||
"type": "request",
|
||||
"action": "get_controllers",
|
||||
"params": {}
|
||||
}
|
||||
```
|
||||
|
||||
### 回應格式
|
||||
```json
|
||||
{
|
||||
"id": "uuid-v4",
|
||||
"type": "response",
|
||||
"status": "success",
|
||||
"data": { ... },
|
||||
"error": null
|
||||
}
|
||||
```
|
||||
|
||||
### 可用操作
|
||||
|
||||
| Action | Description |
|
||||
|--------|-------------|
|
||||
| `get_controllers` | 取得所有控制器 |
|
||||
| `get_raids` | 取得 RAID 陣列 |
|
||||
| `get_disks` | 取得實體磁碟 |
|
||||
| `get_events` | 取得事件日誌 |
|
||||
| `ping` | 健康檢查 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 編譯與執行
|
||||
|
||||
### 5.1 RAIDGuardX (Java)
|
||||
|
||||
```bash
|
||||
cd RAIDGuardX
|
||||
mvn compile # 編譯
|
||||
mvn exec:java -Dexec.mainClass="raidguard_x.AppMain" # 執行
|
||||
mvn clean package # 打包 macOS app
|
||||
```
|
||||
|
||||
### 5.2 Rust GUI (Client + Server)
|
||||
|
||||
```bash
|
||||
# 編譯整個 workspace
|
||||
cargo build --release
|
||||
|
||||
# 分別編譯
|
||||
cd raidguard_x_gui_server
|
||||
cargo build --release
|
||||
|
||||
cd raidguard_x_gui_client
|
||||
cargo build --release
|
||||
|
||||
# 執行伺服器 (預設 port 8923)
|
||||
cd raidguard_x_gui_server
|
||||
cargo run --release
|
||||
|
||||
# 自訂 port
|
||||
SERVER_ADDR=127.0.0.1:8923 cargo run --release
|
||||
|
||||
# 執行客戶端
|
||||
cd raidguard_x_gui_client
|
||||
cargo run --release
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 依賴管理
|
||||
|
||||
### Java 依賴
|
||||
- JUnit 3.8.1 (測試)
|
||||
- javax.mail 1.6.2 (郵件功能)
|
||||
- org.json (JSON 解析)
|
||||
|
||||
### Rust 依賴
|
||||
- slint (UI framework)
|
||||
- tokio (非同步 runtime)
|
||||
- serde (序列化)
|
||||
- 詳細請參考各元件的 Cargo.toml
|
||||
|
||||
---
|
||||
|
||||
## 7. 測試
|
||||
|
||||
### Rust 測試
|
||||
```bash
|
||||
# 測試伺服器
|
||||
nc localhost 8923
|
||||
{"id":"test","type":"request","action":"get_controllers","params":{}}
|
||||
|
||||
# 預期回應
|
||||
{"id":"test","type":"response","status":"success","data":{"controllers":[...]}}
|
||||
```
|
||||
|
||||
### Java 測試
|
||||
```bash
|
||||
cd RAIDGuardX
|
||||
mvn test # 執行所有測試
|
||||
mvn test -Dtest=TestClassName # 執行特定測試
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 開發指南
|
||||
|
||||
### 代碼風格
|
||||
|
||||
**Java (RAIDGuardX)**:
|
||||
- 縮排: 4 spaces
|
||||
- 命名: PascalCase (類別), camelCase (方法/變數)
|
||||
- 前綴: `s` (String), `i` (int), `b/byte` (byte arrays)
|
||||
|
||||
**Rust**:
|
||||
- 遵循 standard Rust idioms
|
||||
- 使用 `rustfmt` 格式化
|
||||
- 使用 `clippy` 進行 lint
|
||||
|
||||
### 調試
|
||||
|
||||
使用 `Debug` 類別進行日誌記錄:
|
||||
```java
|
||||
Debug.Log(">>> [Protocol] GET_PAGE: 0x%02X (%s)", pageCode, pageName);
|
||||
Debug.HexDump("RAW RECEIVED DATA", actualRead);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 版本資訊
|
||||
|
||||
- **Version**: 3.8.0
|
||||
- **License**: Proprietary - Accusys Inc.
|
||||
- **Platform**: macOS, Windows, Linux (Rust), Java 17
|
||||
|
||||
---
|
||||
|
||||
## 10. 發展歷程
|
||||
|
||||
| 版本 | 日期 | 描述 |
|
||||
|------|------|------|
|
||||
| 3.8.0 | 2026-03 | 新增 Rust GUI 客戶端/伺服器 |
|
||||
| 3.7.x | 早期 | Java Swing 版本 |
|
||||
|
||||
---
|
||||
|
||||
## 11. 建議與展望
|
||||
|
||||
1. **統一協議**: 確保 Java 和 Rust 客戶端使用一致的 API 協議
|
||||
2. **Mock 數據**: 目前 GUI 伺服器使用 Mock 數據,建議連接真實 In-Band API
|
||||
3. **Web 介面**: 可考慮開發 Web 版本的 RAID 管理介面
|
||||
4. **容器化**: 考慮使用 Docker 部署 Rust 伺服器元件
|
||||
|
||||
---
|
||||
|
||||
*文件建立時間: 2026-03-26*
|
||||
*Author: Accusys Development Team*
|
||||
@@ -0,0 +1,502 @@
|
||||
# Dump Controller Log 實現分析
|
||||
|
||||
## 流程架構
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ frmMain.jmDumpEventLog_actionPerformed() │
|
||||
│ (根據型號選擇使用 dlgDumpLog 或 dlgDumpLog2) │
|
||||
└──────────────────────────┬──────────────────────────────────┘
|
||||
│
|
||||
┌───────────────┴───────────────┐
|
||||
▼ ▼
|
||||
┌──────────────┐ ┌──────────────┐
|
||||
│ dlgDumpLog │ │ dlgDumpLog2 │
|
||||
│ (舊型號) │ │ (新型號) │
|
||||
└──────────────┘ └──────────────┘
|
||||
│ │
|
||||
└───────────┬───────────────────┘
|
||||
▼
|
||||
┌──────────────────────────┐
|
||||
│ dumpEventLog() │
|
||||
│ TCP 連線 Port 8922 │
|
||||
└───────────┬──────────────┘
|
||||
▼
|
||||
┌──────────────────────────┐
|
||||
│ OpCode: 0x01 │
|
||||
│ SubOp: 0x03 │
|
||||
│ Page: 0x60 ~ 0x64 │
|
||||
└───────────┬──────────────┘
|
||||
▼
|
||||
┌──────────────────────────┐
|
||||
│ 寫入 ./Log 目錄 │
|
||||
│ 壓縮為 ZIP 檔 │
|
||||
└──────────────────────────┘
|
||||
```
|
||||
|
||||
## 通訊協定
|
||||
|
||||
### Request
|
||||
|
||||
```
|
||||
Head: [0x01, 0x17, 0x00, 0x00, 0x00] (23 bytes)
|
||||
Data: [0x02, 0x01, SN(16 bytes), CDB(5 bytes)]
|
||||
```
|
||||
|
||||
### CDB 結構
|
||||
|
||||
| Offset | 值 | 說明 |
|
||||
|--------|-----|------|
|
||||
| 0 | 0x01 | OpCode - GET_PAGE |
|
||||
| 1 | 0x03 | SubOp |
|
||||
| 2 | 0x60-0x64 | Page Code (Log Type) |
|
||||
| 3 | LSB | Block Index |
|
||||
| 4 | MSB | Block Index |
|
||||
|
||||
### Log Types
|
||||
|
||||
| Page | 說明 |
|
||||
|------|------|
|
||||
| 0x60 | System Log |
|
||||
| 0x61 | Event Log |
|
||||
| 0x62 | I/O Error Log |
|
||||
| 0x63 | Dump Log |
|
||||
| 0x64 | Reserved |
|
||||
|
||||
## 實現細節
|
||||
|
||||
### dlgDumpLog.java
|
||||
|
||||
```java
|
||||
// 1. 連線到控制器
|
||||
theSocket.connect(new InetSocketAddress(PageDataDB.GuiSrvIpAddr, Port), 10000);
|
||||
|
||||
// 2. 發送请求 (OpCode 0x01, SubOp 0x03)
|
||||
Cdb[0] = 0x01; // GET_PAGE
|
||||
Cdb[1] = 0x03; // SubOp
|
||||
Cdb[2] = 0x60; // Log type (0x60-0x64)
|
||||
Cdb[3] = BlockIndexLSB;
|
||||
Cdb[4] = BlockIndexMSB;
|
||||
|
||||
// 3. 解析回應
|
||||
arg[0] = reply[9]; // Status (0x50 = Success)
|
||||
arg[1] = reply[12]; // Page code
|
||||
arg[2] = startBlock;
|
||||
arg[3] = totalBlocks;
|
||||
|
||||
// 4. 寫入檔案 (./Log 目錄)
|
||||
File file = getFile((byte)arg[1]);
|
||||
fileOut.write(reply, 15, 512); // 寫入 512 bytes
|
||||
|
||||
// 5. 壓縮成 ZIP
|
||||
compressLog(); // 打包成 Log_YYYYMMDD_HHMMSS.zip
|
||||
```
|
||||
|
||||
### 迴圈邏輯
|
||||
|
||||
```java
|
||||
// 遍历 5 種 log 類型
|
||||
for (byte i = 0x60; i <= 0x64; i++) {
|
||||
// 取得該類型的 log 總量
|
||||
arg1 = dumpEventLog(Cdb);
|
||||
|
||||
// 遍历每個 block
|
||||
for (int j = arg1[2]; j < arg1[3]; j++) {
|
||||
Cdb[3] = (byte)(j & 0xFF); // LSB
|
||||
Cdb[4] = (byte)((j >> 8) & 0xFF); // MSB
|
||||
arg2 = dumpEventLog(Cdb);
|
||||
|
||||
// 寫入單個 block
|
||||
fileOut.write(reply, 15, 512);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 輸出檔案
|
||||
|
||||
- **目錄**: `./Log/`
|
||||
- **格式**: `Log_YYYYMMDD_HHMMSS.zip`
|
||||
- **內容**: 5 個 log 類型的原始資料 (各 512 bytes/block)
|
||||
|
||||
## 關鍵類
|
||||
|
||||
| 類別 | 檔案 | 功能 |
|
||||
|------|------|------|
|
||||
| `frmMain` | frmMain.java:4136 | 入口,選擇 dialog 版本 |
|
||||
| `dlgDumpLog` | dlgDumpLog.java | 舊型號使用 (單一控制器) |
|
||||
| `dlgDumpLog2` | dlgDumpLog2.java | 新型號使用 (支援多控制器) |
|
||||
| `SendCmd` | InBandAPI/SendCmd.java | TCP 通訊底層 |
|
||||
|
||||
## Code Flow
|
||||
|
||||
### 1. Menu 點擊
|
||||
```
|
||||
frmMain.jmDumpControllerLog (MenuItem)
|
||||
→ frmMain.jmDumpEventLog_actionPerformed()
|
||||
→ 檢查型號選擇 dlgDumpLog 或 dlgDumpLog2
|
||||
```
|
||||
|
||||
### 2. Dump 執行
|
||||
```
|
||||
dlgDumpLog.run()
|
||||
→ for (0x60 ~ 0x64) {
|
||||
dumpEventLog() 取得總量
|
||||
for (每個 block) {
|
||||
dumpEventLog() 取得資料
|
||||
寫入檔案
|
||||
}
|
||||
}
|
||||
→ compressLog() 壓縮
|
||||
→ dispose()
|
||||
```
|
||||
|
||||
### 3. Socket 通訊
|
||||
```
|
||||
dumpEventLog(Cdb)
|
||||
→ connect(Port 8922)
|
||||
→ send(Head + SN + CDB)
|
||||
→ read(reply)
|
||||
→ parse status & data
|
||||
→ close()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ZIP 壓縮處理細節
|
||||
|
||||
### 輸出檔案
|
||||
|
||||
**dlgDumpLog.java (舊型號)**
|
||||
```
|
||||
./Log/{SN}_ControllerLog_{YYYY}_{M}_{D}_{H}_{m}_{s}.zip
|
||||
```
|
||||
|
||||
**dlgDumpLog2.java (新型號)**
|
||||
```
|
||||
Primary: ./Log/Primary_{SN}_ControllerLog.zip
|
||||
Secondary: ./Log/Secondary_{SN}_ControllerLog.zip
|
||||
合併: ./Log/{PrimarySN}_{SecondarySN}_ControllerLog_{YYYY}_{M}_{D}_{H}_{m}_{s}.zip
|
||||
```
|
||||
|
||||
### 壓縮前的暫存檔
|
||||
|
||||
| Page Code | 檔案名稱 | 說明 |
|
||||
|-----------|----------|------|
|
||||
| 0x60 | EventPage.bin | Event Log |
|
||||
| 0x61 | DebugPage.bin | Debug Log |
|
||||
| 0x62 | DiskMetadataPage.bin | Disk Metadata |
|
||||
| 0x63 | SpecialLogPage.bin | Special Log |
|
||||
| 0x64 | TelMesPages.log | Telemetry Messages |
|
||||
|
||||
### compressLog() 流程 (dlgDumpLog.java)
|
||||
|
||||
```java
|
||||
public void compressLog() {
|
||||
File[] logs = new File[5];
|
||||
for(int i=0; i<=4; i++) {
|
||||
logs[i] = getFile((byte)(i+0x60)); // 取得 5 個暫存檔
|
||||
}
|
||||
|
||||
// 建立 ZIP 檔案
|
||||
Calendar rightNow = Calendar.getInstance();
|
||||
String filename = "./Log/" + PageDataDB.sSN.trim() + "_ControllerLog_"
|
||||
+ rightNow.get(Calendar.YEAR) + "_"
|
||||
+ (rightNow.get(Calendar.MONTH)+1) + "_"
|
||||
+ rightNow.get(Calendar.DAY_OF_MONTH) + "_"
|
||||
+ rightNow.get(Calendar.HOUR) + "_"
|
||||
+ rightNow.get(Calendar.MINUTE) + "_"
|
||||
+ rightNow.get(Calendar.SECOND) + ".zip";
|
||||
|
||||
FileOutputStream fout = new FileOutputStream(filename);
|
||||
ZipOutputStream zout = new ZipOutputStream(fout);
|
||||
zout.setLevel(5); // 壓縮等級 0-9
|
||||
|
||||
// 逐一壓縮每個檔案
|
||||
for (byte i = 0; i <= 4; i++) {
|
||||
if(logs[i].exists()) {
|
||||
ZipEntry ze = new ZipEntry(logs[i].getName());
|
||||
FileInputStream fin = new FileInputStream(logs[i]);
|
||||
zout.putNextEntry(ze);
|
||||
|
||||
// 特殊處理 Page 0x64 (TelMesPages.log)
|
||||
if(i != 0x4) {
|
||||
// 一般檔案:直接複製
|
||||
for(int c = fin.read(); c != -1; c = fin.read()) {
|
||||
zout.write(c);
|
||||
}
|
||||
} else {
|
||||
// TelMesPages.log: 每 40 bytes 為一筆記錄
|
||||
// 移除空字元 (0x00),只保留有效資料
|
||||
byte data;
|
||||
boolean skipNull = false;
|
||||
for(int j=0; j < logs[i].length()/40; j++) {
|
||||
skipNull = false;
|
||||
for(int k=0; k<40; k++) {
|
||||
data = (byte)fin.read();
|
||||
if(data == 0x0a || skipNull == true) {
|
||||
skipNull = true;
|
||||
} else {
|
||||
zout.write(data);
|
||||
}
|
||||
}
|
||||
zout.write(0x0a); // 加上換行符
|
||||
}
|
||||
}
|
||||
|
||||
zout.closeEntry();
|
||||
fin.close();
|
||||
logs[i].delete(); // 刪除暫存檔
|
||||
}
|
||||
}
|
||||
zout.close();
|
||||
}
|
||||
```
|
||||
|
||||
### compressLog() 流程 (dlgDumpLog2.java)
|
||||
|
||||
```java
|
||||
// 單一控制器壓縮
|
||||
public void compressLog(String sSN) {
|
||||
String prefix = sSN.equals(PageDataDB.sSN) ? "Primary" : "Secondary";
|
||||
String filename = "./Log/" + prefix + "_" + sSN.trim() + "_ControllerLog.zip";
|
||||
// ... 同上邏輯
|
||||
}
|
||||
|
||||
// 雙控制器合併壓縮
|
||||
public void compressLog2() {
|
||||
File[] logs = new File[2];
|
||||
logs[0] = new File(sMaterFileNamePatch); // Primary
|
||||
logs[1] = new File(sSlaveFileNamePatch); // Secondary
|
||||
|
||||
// 合併成單一 ZIP,內含兩個 ZIP 檔
|
||||
String filename = "./Log/" + PageDataDB.sSN.trim() + "_"
|
||||
+ PageDataDB.sSlaveSN.trim() + "_ControllerLog_{timestamp}.zip";
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### ZIP 結構
|
||||
|
||||
```
|
||||
Archive: {SN}_ControllerLog_2024_3_29_10_30_45.zip
|
||||
├── EventPage.bin (512 bytes/block)
|
||||
├── DebugPage.bin (512 bytes/block)
|
||||
├── DiskMetadataPage.bin (512 bytes/block)
|
||||
├── SpecialLogPage.bin (512 bytes/block)
|
||||
└── TelMesPages.log (40 bytes/record, 去除 null)
|
||||
```
|
||||
|
||||
### 特殊處理:TelMesPages.log (Page 0x64)
|
||||
|
||||
```java
|
||||
// 原始資料: 40 bytes/record,包含 null (0x00) 字元
|
||||
// 處理邏輯:
|
||||
// 1. 讀取每 40 bytes
|
||||
// 2. 跳過開頭的 null 字元
|
||||
// 3. 遇到換行符 (0x0a) 後,後續全部視為有效資料
|
||||
// 4. 輸出時每筆記錄加上換行符
|
||||
|
||||
// Example:
|
||||
// Input: [0x00, 0x00, 0x00, 0x31, 0x32, ..., 0x00, 0x00]
|
||||
// Output: "12...data\n"
|
||||
```
|
||||
|
||||
### Java 類別使用
|
||||
|
||||
```java
|
||||
import java.util.zip.ZipOutputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.Calendar;
|
||||
|
||||
// 關鍵類別
|
||||
ZipOutputStream zout = new ZipOutputStream(fout);
|
||||
zout.setLevel(5); // 壓縮等級 (0=儲存, 9=最大壓縮)
|
||||
ZipEntry ze = new ZipEntry(filename);
|
||||
zout.putNextEntry(ze);
|
||||
zout.write(byte[] data);
|
||||
zout.closeEntry();
|
||||
zout.close();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## .bin 檔案解析
|
||||
|
||||
### 原始資料格式
|
||||
|
||||
控制器回傳的資料包含 **Header + Data**:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Controller Response │
|
||||
├─────────┬───────────────────────────────────┤
|
||||
│ Offset │ Description │
|
||||
├─────────┼───────────────────────────────────┤
|
||||
│ 0-2 │ Total Length (2 bytes) │
|
||||
│ 3 │ (continuation?) │
|
||||
│ 4-8 │ Unknown │
|
||||
│ 9 │ Status (0x50 = Success) │
|
||||
│ 10-11 │ Unknown │
|
||||
│ 12 │ Page Code (0x60-0x64) │
|
||||
│ 13-14 │ 0xFF 0xFF (flag) │
|
||||
│ 15-18 │ Log Count (4 bytes) │
|
||||
│ 19-22 │ Start Block (4 bytes) │
|
||||
│ 23-26 │ Total Blocks (4 bytes) │
|
||||
├─────────┴───────────────────────────────────┤
|
||||
│ 15-511 │ Actual Data (496 bytes) │
|
||||
└─────────┴───────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 寫入 .bin 檔案的程式碼
|
||||
|
||||
```java
|
||||
// 從 reply[15] 開始,寫入 512 bytes
|
||||
fileOut.write(reply, 15, 512);
|
||||
```
|
||||
|
||||
所以 .bin 檔案內容是:
|
||||
- **每筆 512 bytes**
|
||||
- **包含 header 資訊**
|
||||
|
||||
### 解析方式
|
||||
|
||||
#### 1. 解析 Header
|
||||
|
||||
```python
|
||||
def parse_bin_header(block):
|
||||
# block 是 512 bytes
|
||||
status = block[9] # 0x50 = Success
|
||||
page_code = block[12] # 0x60-0x64
|
||||
log_count = int.from_bytes(block[15:19], 'little')
|
||||
start_block = int.from_bytes(block[19:23], 'little')
|
||||
total_blocks = int.from_bytes(block[23:27], 'little')
|
||||
data = block[15:] # 實際資料
|
||||
|
||||
return {
|
||||
'status': status,
|
||||
'page_code': page_code,
|
||||
'log_count': log_count,
|
||||
'start_block': start_block,
|
||||
'total_blocks': total_blocks,
|
||||
'data': data
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 解析各類型 Log
|
||||
|
||||
| Page Code | 類型 | 資料格式 |
|
||||
|-----------|------|----------|
|
||||
| 0x60 | EventPage.bin | 32 bytes/record |
|
||||
| 0x61 | DebugPage.bin | 32 bytes/record |
|
||||
| 0x62 | DiskMetadataPage.bin | 32 bytes/record |
|
||||
| 0x63 | SpecialLogPage.bin | 48 bytes/record |
|
||||
| 0x64 | TelMesPages.log | 40 bytes/record (特殊處理) |
|
||||
|
||||
#### 3. Python 解析範例
|
||||
|
||||
```python
|
||||
import struct
|
||||
import datetime
|
||||
|
||||
def parse_event_log(data):
|
||||
"""解析 Event Page (0x60)"""
|
||||
records = []
|
||||
for i in range(0, len(data), 32):
|
||||
if i + 32 > len(data):
|
||||
break
|
||||
record = data[i:i+32]
|
||||
|
||||
# 解析 timestamp (假設格式)
|
||||
timestamp = struct.unpack('<I', record[0:4])[0]
|
||||
event_id = struct.unpack('<H', record[4:6])[0]
|
||||
message = record[6:].decode('ascii', errors='replace').rstrip('\x00')
|
||||
|
||||
records.append({
|
||||
'timestamp': timestamp,
|
||||
'event_id': event_id,
|
||||
'message': message
|
||||
})
|
||||
|
||||
return records
|
||||
|
||||
# 使用
|
||||
with open('EventPage.bin', 'rb') as f:
|
||||
data = f.read()
|
||||
events = parse_event_log(data)
|
||||
for e in events:
|
||||
print(f"{e['event_id']}: {e['message']}")
|
||||
```
|
||||
|
||||
#### 4. 用 Hexdump 查看
|
||||
|
||||
```bash
|
||||
# 查看前 512 bytes
|
||||
xxd -l 512 EventPage.bin
|
||||
|
||||
# 輸出範例:
|
||||
00000000: 5001 0000 0000 0000 0000 5001 ff ff 0000 0000 P...........P....
|
||||
00000010: 0000 0001 0000 0001 0000 0000 0000 0000 ................
|
||||
00000020: 3031 3233 3435 3637 3839 3031 3233 3435 0123456789012345
|
||||
00000030: 3637 3839 0000 0000 0000 0000 0000 0000 6789............
|
||||
...
|
||||
```
|
||||
|
||||
### TelMesPages.log 特殊處理
|
||||
|
||||
Page 0x64 有特殊處理:
|
||||
- 原始:40 bytes/record
|
||||
- 輸出:去除 null 字元 (0x00)
|
||||
|
||||
```java
|
||||
// 寫入時的處理
|
||||
for(int j=0; j < logs[i].length()/40; j++) {
|
||||
for(int k=0; k<40; k++) {
|
||||
data = (byte)fin.read();
|
||||
if(data == 0x0a || skipNull == true) {
|
||||
skipNull = true;
|
||||
} else {
|
||||
zout.write(data);
|
||||
}
|
||||
}
|
||||
zout.write(0x0a); // 加上換行
|
||||
}
|
||||
```
|
||||
|
||||
### Rust 解析範例
|
||||
|
||||
```rust
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct LogHeader {
|
||||
status: u8,
|
||||
page_code: u8,
|
||||
log_count: u32,
|
||||
start_block: u32,
|
||||
total_blocks: u32,
|
||||
}
|
||||
|
||||
fn parse_bin_header(block: &[u8; 512]) -> LogHeader {
|
||||
LogHeader {
|
||||
status: block[9],
|
||||
page_code: block[12],
|
||||
log_count: u32::from_le_bytes([block[15], block[16], block[17], block[18]]),
|
||||
start_block: u32::from_le_bytes([block[19], block[20], block[21], block[22]]),
|
||||
total_blocks: u32::from_le_bytes([block[23], block[24], block[25], block[26]]),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut file = File::open("EventPage.bin").unwrap();
|
||||
let mut buffer = [0u8; 512];
|
||||
|
||||
let mut block_num = 0;
|
||||
while file.read(&mut buffer).unwrap() > 0 {
|
||||
let header = parse_bin_header(&buffer);
|
||||
println!("Block {}: status={:02x}, page={:02x}",
|
||||
block_num, header.status, header.page_code);
|
||||
block_num += 1;
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,305 @@
|
||||
# Email Alert 架構分析
|
||||
|
||||
## 概述
|
||||
|
||||
RAIDGuard X 支援透過 SMTP 發送郵件通知功能,包括:
|
||||
- 手動發送測試郵件
|
||||
- 事件觸發自動郵件通知
|
||||
|
||||
## 架構流程
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Email Alert 架構 │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ GUI │ │ Controller │ │ SMTP Server │ │
|
||||
│ │ frmEmail │◄──────►│ (Port 8922)│ │ │ │
|
||||
│ └──────┬───────┘ └──────────────┘ └──────┬───────┘ │
|
||||
│ │ │ │
|
||||
│ │ In-Band API (OpCode 0x01) │ │
|
||||
│ │ - SMTP 伺服器 │ │
|
||||
│ │ - SMTP 使用者名稱/密碼 │ │
|
||||
│ │ - 寄件人/收件人 │ │
|
||||
│ │ - 啟用事件通知 │ │
|
||||
│ └────────────────────────┬───────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────────────────┐ │
|
||||
│ │ JavaMail API (javax.mail) │ │
|
||||
│ │ - Session.getInstance() │ │
|
||||
│ │ - MimeMessage │ │
|
||||
│ │ - Transport.send() │ │
|
||||
│ └──────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 設定介面 (frmEmail)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Email Settings │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ Mailing List: [Send Test Email] │
|
||||
│ ┌─────────────────────────────────────────┐ │
|
||||
│ │ user1@example.com │ [Remove] │
|
||||
│ │ user2@example.com │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ SMTP Settings: │
|
||||
│ ┌───────────────────────────────────────────────────────────┐ │
|
||||
│ │ Mail Server (SMTP): [smtp.example.com ] │ │
|
||||
│ │ From Email: [sender@example.com ] │ │
|
||||
│ │ ☐ SMTP Auth Username: [__________] │ │
|
||||
│ │ Password: [__________] │ │
|
||||
│ │ ☐ Post Event to Email (Enable auto-alert) │ │
|
||||
│ └───────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [OK] [Cancel] │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 儲存的設定
|
||||
|
||||
透過 In-Band API 儲存到控制器:
|
||||
|
||||
| OpCode | 說明 |
|
||||
|--------|------|
|
||||
| 0x07 | 更新 SMTP 伺服器名稱 |
|
||||
| 0x09 | 更新 SMTP 使用者名稱 |
|
||||
| 0x0A | 更新 SMTP 密碼 |
|
||||
| 0x0B | 取得 SMTP 使用者名稱 |
|
||||
| 0x0C | 取得 SMTP 密碼 |
|
||||
| 0x05 | 取得 SMTP 伺服器名稱 |
|
||||
| 0x0E | 儲存 Post Event 設定 |
|
||||
| 0x19 | 取得 Post Event 設定 |
|
||||
|
||||
## 程式碼分析
|
||||
|
||||
### 1. 郵件發送 (frmEmail.java)
|
||||
|
||||
```java
|
||||
// 使用 JavaMail API
|
||||
import javax.mail.*;
|
||||
import javax.mail.internet.*;
|
||||
|
||||
public void jbSendMail_actionPerformed(ActionEvent e) {
|
||||
// 建立 Session
|
||||
Properties props = new Properties();
|
||||
props.put("mail.smtp.host", this.jtServerIp.getText());
|
||||
|
||||
if (jcSMTPCheck.isSelected()) {
|
||||
// SMTP 認證
|
||||
props.put("mail.smtp.auth", "true");
|
||||
props.put("mail.smtp.user", sUsername);
|
||||
props.put("mail.smtp.password", sPassword);
|
||||
session = Session.getInstance(props, new Authenticator() {
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(sUsername, sPassword);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 無認證
|
||||
props.put("mail.smtp.auth", "false");
|
||||
session = Session.getInstance(props);
|
||||
}
|
||||
|
||||
// 建立郵件
|
||||
Message msg = new MimeMessage(session);
|
||||
msg.setFrom(new InternetAddress(sNameMailFrom));
|
||||
msg.setSubject(sMailTitle);
|
||||
msg.setText(sMailBody);
|
||||
msg.setSentDate(new Date());
|
||||
|
||||
// 發送到所有收件人
|
||||
for (int j = 0; j < dmMailList.getRowCount(); j++) {
|
||||
sNameMailTo = dmMailList.getValueAt(j, 0).toString().trim();
|
||||
if (!sNameMailTo.equals("")) {
|
||||
msg.setRecipients(Message.RecipientType.TO,
|
||||
InternetAddress.parse(sNameMailTo, false));
|
||||
Transport.send(msg); // 發送郵件
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. SMTP 認證設定
|
||||
|
||||
```java
|
||||
// 有認證
|
||||
if (jcSMTPCheck.isSelected()) {
|
||||
props.put("mail.smtp.auth", "true");
|
||||
props.put("mail.smtp.ssl.enable", "true"); // SSL
|
||||
session = Session.getInstance(props, new Authenticator() {
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(username, password);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 無認證
|
||||
else {
|
||||
props.put("mail.smtp.host", smtpServer);
|
||||
props.put("mail.smtp.auth", "false");
|
||||
session = Session.getInstance(props);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 事件通知開關
|
||||
|
||||
```java
|
||||
// 儲存 Post Event 設定到控制器
|
||||
bCmds[2] = 0x0E; // OpCode
|
||||
|
||||
if (jcPostEvent.isSelected()) {
|
||||
bCmds[4] = 1; // Enable
|
||||
} else {
|
||||
bCmds[4] = 0; // Disable
|
||||
}
|
||||
|
||||
// 透過 CmdQueue 發送到控制器
|
||||
CmdQueue.NewCommand(ip, sn, 1, 1, 0x01);
|
||||
```
|
||||
|
||||
## 依賴庫
|
||||
|
||||
```xml
|
||||
<!-- pom.xml -->
|
||||
<dependency>
|
||||
<groupId>com.sun.mail</groupId>
|
||||
<artifactId>javax.mail</artifactId>
|
||||
<version>1.6.2</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Polling 與事件觸發
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Polling Timer (定時輪詢) │
|
||||
│ - wkPolling1: 每 12 秒 │
|
||||
│ - wkPolling2: 每 30 秒 │
|
||||
│ - wkEventPolling: 每 18 秒 │
|
||||
│ - wkEventPollingForSNMP: 每 22 秒 │
|
||||
└──────────────────────────┬──────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 檢測事件 │
|
||||
│ 1. 取得控制器事件 (Page 0x24) │
|
||||
│ 2. 比對新事件 │
|
||||
│ 3. 觸發通知 │
|
||||
└──────────────────────────┬──────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 通知方式 │
|
||||
│ - SNMP Trap (frmMain.EventPollingForSNMP) │
|
||||
│ - Email Alert (如果 jcPostEvent 啟用) │
|
||||
│ - UI Event List 更新 │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 關鍵類
|
||||
|
||||
| 類別 | 檔案 | 功能 |
|
||||
|------|------|------|
|
||||
| `frmEmail` | frmEmail.java | 郵件設定介面 |
|
||||
| `dlgServer` | dlgServer.java | Server 設定 (包含 SMTP) |
|
||||
| `PageDataDB` | PageDataDB.java | 郵件設定儲存 |
|
||||
| `SNMPSender` | SNMP/SNMPSender.java | SNMP Trap 發送 |
|
||||
|
||||
## Code Flow
|
||||
|
||||
### 1. 設定 SMTP
|
||||
```
|
||||
frmEmail (開啟)
|
||||
→ 輸入 SMTP 伺服器
|
||||
→ 輸入寄件人/收件人
|
||||
→ (可選) 啟用 SMTP 認證
|
||||
→ (可選) 啟用 Post Event
|
||||
→ OK → 儲存到控制器 (OpCode 0x07/0x09/0x0A/0x0E)
|
||||
```
|
||||
|
||||
### 2. 發送測試郵件
|
||||
```
|
||||
點擊 [Send Test Email]
|
||||
→ frmEmail.jbSendMail_actionPerformed()
|
||||
→ 建立 Session (JavaMail)
|
||||
→ 發送到所有收件人
|
||||
→ 顯示成功/失敗訊息
|
||||
```
|
||||
|
||||
### 3. 事件自動通知
|
||||
```
|
||||
Polling 偵測到新事件
|
||||
→ 檢查 jcPostEvent 是否啟用
|
||||
→ 組建郵件內容 (事件描述)
|
||||
→ 發送到所有收件人
|
||||
```
|
||||
|
||||
## 郵件格式
|
||||
|
||||
```
|
||||
Subject: RAIDGuard X Alert - {Controller Name}
|
||||
|
||||
Body:
|
||||
Controller: {SN}
|
||||
Event: {Event Description}
|
||||
Time: {Timestamp}
|
||||
Severity: {Info/Warning/Error}
|
||||
```
|
||||
|
||||
## 安全考量
|
||||
|
||||
1. **密碼儲存**: SMTP 密碼儲存在控制器端,可能有安全風險
|
||||
2. **傳輸加密**: 支援 SSL/TLS (jcSMTPCheck)
|
||||
3. **認證**: 支援 SMTP AUTH (PLAIN/LOGIN)
|
||||
|
||||
## Rust 實作建議
|
||||
|
||||
```rust
|
||||
// 使用 lettre crate
|
||||
use lettre::{SmtpClient, Transport, Message, SendableEmail};
|
||||
use lettre::smtp::authentication::Credentials;
|
||||
|
||||
pub fn send_email(
|
||||
smtp_server: &str,
|
||||
username: &str,
|
||||
password: &str,
|
||||
from: &str,
|
||||
to: &str,
|
||||
subject: &str,
|
||||
body: &str,
|
||||
use_auth: bool,
|
||||
use_tls: bool,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
let email = Message::builder()
|
||||
.from(from.parse()?)
|
||||
.to(to.parse()?)
|
||||
.subject(subject)
|
||||
.body(body.to_string())?;
|
||||
|
||||
let mut smtp = SmtpClient::new_localhost()?;
|
||||
|
||||
if use_tls {
|
||||
smtp = SmtpClient::new_new_ssl(smtp_server)?;
|
||||
} else {
|
||||
smtp = SmtpClient::new_new(smtp_server)?;
|
||||
}
|
||||
|
||||
if use_auth {
|
||||
let creds = Credentials::new(username.to_string(), password.to_string());
|
||||
smtp = smtp.credentials(creds);
|
||||
}
|
||||
|
||||
let mut transport = smtp.transport();
|
||||
transport.send(email)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,256 @@
|
||||
# 密碼系統分析
|
||||
|
||||
## 概述
|
||||
|
||||
RAIDGuard X 使用密碼系統來控制對 RAID 控制器的訪問權限。以下是密碼相關的功能:
|
||||
|
||||
## 密碼類型
|
||||
|
||||
| 密碼類型 | 說明 | 預設值 |
|
||||
|-----------|------|--------|
|
||||
| 控制器密碼 | 連線到控制器時驗證 | `00000000` |
|
||||
| 進階功能密碼 | 用於進階功能 (如 Update Firmware) | `WANGWANG` 或 `=AU4@A83` |
|
||||
|
||||
## 密碼驗證流程
|
||||
|
||||
### 1. 密碼檢查 (PwdCheck)
|
||||
|
||||
```
|
||||
使用者操作進階功能
|
||||
→ PwdCheck(loc)
|
||||
→ 檢查 sPwd[SN] 是否為空
|
||||
→ 若為空,彈出密碼輸入對話框 (dlgPasswordCheck)
|
||||
→ 驗證密碼正確性
|
||||
→ 成功後才允許執行
|
||||
```
|
||||
|
||||
### 2. 密碼輸入對話框 (dlgPasswordCheck)
|
||||
|
||||
```java
|
||||
// dlgPasswordCheck.java
|
||||
public void jbOk_actionPerformed() {
|
||||
// 取得使用者輸入的密碼 (8 字元)
|
||||
String inputPwd = jpPwd.getSelectedText();
|
||||
|
||||
// 儲存到 PageDataDB.sPwd[]
|
||||
PageDataDB.sPwd[SNToNo(sSN)] = inputPwd + " ";
|
||||
|
||||
// 發送密碼到控制器驗證
|
||||
// OpCode: 0x1D
|
||||
// SubOp: 0x08
|
||||
// 密碼: raw char values (無轉換)
|
||||
bCmds[3] = 0x1D; // OpCode
|
||||
bCmds[4] = 0x08; // Length
|
||||
bCmds[5-12] = password bytes (charAt(i))
|
||||
}
|
||||
```
|
||||
|
||||
## 密碼發送機制
|
||||
|
||||
### OpCode: 0x1D - SEND_PASSWORD
|
||||
|
||||
**CDB 結構:**
|
||||
|
||||
| Offset | 值 | 說明 |
|
||||
|--------|-----|------|
|
||||
| 0 | 0x00 | Phase Code |
|
||||
| 1 | 0x00 | Request Code |
|
||||
| 2 | 0x00 | Controller No |
|
||||
| 3 | 0x1D | OpCode - SEND_PASSWORD |
|
||||
| 4 | 0x08 | Length (8 bytes) |
|
||||
| 5-12 | password | 8 bytes password |
|
||||
|
||||
### 密碼位元組轉換
|
||||
|
||||
**有兩種不同的轉換方式:**
|
||||
|
||||
#### 方式 1: dlgPasswordCheck (直接發送)
|
||||
```java
|
||||
// 密碼直接作為 char 發送
|
||||
bCmds[5] = (byte) password.charAt(0);
|
||||
bCmds[6] = (byte) password.charAt(1);
|
||||
// ...
|
||||
```
|
||||
|
||||
#### 方式 2: SendCmd.sendPassword (減法轉換)
|
||||
```java
|
||||
// 密碼字元減去 0x30
|
||||
pwdBytes[i] = (byte) (password.charAt(i) - 0x30);
|
||||
|
||||
// Example:
|
||||
// '0' (0x30) → 0x00
|
||||
// '1' (0x31) → 0x01
|
||||
// '9' (0x39) → 0x09
|
||||
// 'A' (0x41) → 0x11
|
||||
```
|
||||
|
||||
## 密碼儲存
|
||||
|
||||
### PageDataDB.sPwd[]
|
||||
|
||||
```java
|
||||
// 全域密碼儲存陣列
|
||||
public static String sPwd[] = new String[iMaxHosts];
|
||||
|
||||
// 根據序號查詢密碼索引
|
||||
public static int SNToNo(String sSN) {
|
||||
// 查詢 sSNToNo[][] 對應表
|
||||
// 回傳該 SN 的密碼陣列索引
|
||||
}
|
||||
|
||||
// 儲存密碼
|
||||
PageDataDB.sPwd[SNToNo(sSN)] = "00000000";
|
||||
```
|
||||
|
||||
### 密碼對應表
|
||||
|
||||
```
|
||||
sSNToNo[i][0] = 控制器序號 (String)
|
||||
sSNToNo[i][1] = 密碼索引 (String "0"-"15")
|
||||
```
|
||||
|
||||
## 預設密碼
|
||||
|
||||
### 預設控制器密碼
|
||||
```
|
||||
預設值: "00000000" (8 個零)
|
||||
```
|
||||
|
||||
### 進階功能密碼
|
||||
|
||||
在 `PageDataDB.GetPlayMenu()` 中檢查:
|
||||
```java
|
||||
if (sPwd[SNToNo(sSN)].trim().equals("=AU4@A83") ||
|
||||
sPwd[SNToNo(sSN)].trim().equals("WANGWANG")) {
|
||||
// 啟用進階功能 (Play Menu)
|
||||
}
|
||||
```
|
||||
|
||||
| 密碼 | 用途 |
|
||||
|------|------|
|
||||
| `00000000` | 預設控制器連線密碼 |
|
||||
| `WANGWANG` | 啟用 Play Menu 進階功能 |
|
||||
| `=AU4@A83` | 啟用 Play Menu 進階功能 (另一組) |
|
||||
|
||||
## 需要密碼的功能
|
||||
|
||||
以下功能需要密碼驗證:
|
||||
|
||||
| 功能 | 密碼類型 |
|
||||
|------|----------|
|
||||
| Update System Code (Firmware) | 控制器密碼 |
|
||||
| Update Boot Code | 控制器密碼 |
|
||||
| Update BIOS/EFI | 控制器密碼 |
|
||||
| Update Expander Code | 控制器密碼 |
|
||||
| Dump Controller Log | 控制器密碼 |
|
||||
| Shutdown Controller | 控制器密碼 |
|
||||
| Play Menu | 進階密碼 (`WANGWANG` 或 `=AU4@A83`) |
|
||||
|
||||
## 密碼對話框
|
||||
|
||||
### dlgPasswordCheck
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────┐
|
||||
│ Password Check │
|
||||
│ │
|
||||
│ [________________] │
|
||||
│ (Default Password: 00000000) │
|
||||
│ │
|
||||
│ [OK] [Cancel] │
|
||||
└────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 密碼欄位設定
|
||||
|
||||
```java
|
||||
// 固定長度 8 字元
|
||||
jpPwd.setDocument(new FixedSizePlainDocument(8));
|
||||
jpPwd.setSelectionEnd(8);
|
||||
|
||||
// 密碼可視 (不遮蔽)
|
||||
// jpPwd.setEchoChar('*');
|
||||
```
|
||||
|
||||
## Code Flow
|
||||
|
||||
### 密碼驗證流程
|
||||
|
||||
```
|
||||
1. 使用者點擊功能 (如 Update Firmware)
|
||||
2. frmMain.jmXXX_actionPerformed()
|
||||
3. PageDataDB.PwdCheck(loc)
|
||||
4.
|
||||
a. 若 sPwd[SN] 已有密碼 → 自動使用
|
||||
b. 若 sPwd[SN] 為空 → 彈出 dlgPasswordCheck
|
||||
5.
|
||||
a. 驗證成功 → bPwdCheckPass = true → 執行功能
|
||||
b. 驗證失敗 → bPwdCheckPass = false → 拒絕執行
|
||||
```
|
||||
|
||||
### 密碼發送流程
|
||||
|
||||
```
|
||||
1. dlgPasswordCheck.jbOk_actionPerformed()
|
||||
2. 取得輸入密碼 → 儲存到 sPwd[SN]
|
||||
3. 建立 CmdQueue 命令
|
||||
4.
|
||||
a. dlgPasswordCheck: 使用 raw char (無轉換)
|
||||
b. SendCmd.sendPassword: 使用 char - 0x30 轉換
|
||||
5. 發送到控制器 (OpCode 0x1D)
|
||||
6. 等待回應
|
||||
7. 驗證成功 → 刷新頁面資料
|
||||
```
|
||||
|
||||
## 關鍵類
|
||||
|
||||
| 類別 | 檔案 | 功能 |
|
||||
|------|------|------|
|
||||
| `PageDataDB` | PageDataDB.java | 密碼儲存與驗證 |
|
||||
| `dlgPasswordCheck` | dlgPasswordCheck.java | 密碼輸入對話框 |
|
||||
| `SendCmd` | InBandAPI/SendCmd.java | 密碼發送通訊 |
|
||||
|
||||
## 參考程式碼
|
||||
|
||||
### SendCmd.sendPassword()
|
||||
```java
|
||||
public void sendPassword(String sIP, String sSN) {
|
||||
// 取得密碼
|
||||
String rawPwdStr = PageDataDB.sPwd[SNToNo(sSN)];
|
||||
|
||||
// 密碼轉換: char - 0x30
|
||||
byte[] pwdBytes = new byte[8];
|
||||
for (int i = 0; i < 8; i++) {
|
||||
pwdBytes[i] = (byte) (rawPwdStr.charAt(i) - 0x30);
|
||||
}
|
||||
|
||||
// 發送
|
||||
// OpCode: 0x1D
|
||||
// Length: 0x08
|
||||
// Data: pwdBytes[0..7]
|
||||
this.send(sIP, sSN, (byte) 0x1D, (byte) 0x08,
|
||||
pwdBytes[0], pwdBytes[1], pwdBytes[2], pwdBytes[3],
|
||||
pwdBytes[4], pwdBytes[5], pwdBytes[6], pwdBytes[7]);
|
||||
}
|
||||
```
|
||||
|
||||
### PageDataDB.PwdCheck()
|
||||
```java
|
||||
public static int PwdCheck(Point loc) {
|
||||
if (sSN.trim().equals("")) {
|
||||
return 0x00; // 無控制器
|
||||
}
|
||||
|
||||
if (sPwd[SNToNo(sSN)].equals("")) {
|
||||
// 彈出密碼輸入對話框
|
||||
dlgPasswordCheck dlgPwdCheck = new dlgPasswordCheck();
|
||||
dlgPwdCheck.setVisible(true);
|
||||
|
||||
if (dlgPwdCheck.bPwdCheckPass == false) {
|
||||
return 0x00; // 驗證失敗
|
||||
}
|
||||
}
|
||||
|
||||
return 0x01; // 驗證成功
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,413 @@
|
||||
# Polling 架構分析
|
||||
|
||||
## 概述
|
||||
|
||||
Polling 是 RAIDGuard X 用來定期檢測 RAID 控制器狀態的核心機制,負責:
|
||||
- 取得控制器狀態
|
||||
- 取得事件日誌
|
||||
- 檢測連線狀態
|
||||
- 發送 SNMP Trap
|
||||
|
||||
## 架構流程
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Polling 架構 │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ java.util.Timer (tmPolling) │ │
|
||||
│ │ ┌─────────────┬─────────────┬─────────────┬─────────────┐ │ │
|
||||
│ │ │ wkPolling1 │ wkPolling2 │ wkEventPolling │wkEventPoll │ │ │
|
||||
│ │ │ (12秒) │ (30秒) │ (18秒) │ forSNMP │ │ │
|
||||
│ │ │ │ │ │ (22秒) │ │ │
|
||||
│ │ └─────────────┴─────────────┴─────────────┴─────────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────────────────┼────────────────────┐ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Polling() │ │ GetEvent() │ │EventPollFor │ │
|
||||
│ │ 狀態輪詢 │ │ 事件取得 │ │ SNMP() │ │
|
||||
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
||||
│ │ │ │ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ PageRefresh() │ │IssueCmdAgent2│ │IssueCmdAgent2│ │
|
||||
│ │ 取得頁面資料 │ │ 發送命令 │ │ 發送命令 │ │
|
||||
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
||||
│ │ │ │ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────────────────┐ │
|
||||
│ │ In-Band API (Port 8922) │ │
|
||||
│ │ - 0x01 GET_PAGE │ │
|
||||
│ │ - 0xFF/0x02 Get InBand Event │ │
|
||||
│ └──────────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────────────────┐ │
|
||||
│ │ RAID Controller │ │
|
||||
│ └──────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Polling 任務
|
||||
|
||||
### Timer 排程
|
||||
|
||||
```java
|
||||
// frmMain.java - 1460-1477
|
||||
java.util.Timer tmPolling = new java.util.Timer();
|
||||
|
||||
// 主動式輪詢 (線上狀態)
|
||||
tmPolling.schedule(new wkPolling1(this), 5000, 12001); // 每 12 秒
|
||||
tmPolling.schedule(new wkPolling2(this), 5000, 3001); // 每 3 秒
|
||||
|
||||
// 被動式輪詢 (離線狀態 - 檢查是否重新上線)
|
||||
tmPolling.schedule(new wkStatusRefresh(this), 5000, 1004); // 每 1 秒
|
||||
|
||||
// 事件輪詢
|
||||
tmPolling.schedule(new wkEventPolling(this), 5000, 18003); // 每 18 秒
|
||||
tmPolling.schedule(new wkEventPollingForSNMP(this), 5000, 22002); // 每 22 秒
|
||||
```
|
||||
|
||||
| 任務 | 週期 | 說明 |
|
||||
|------|------|------|
|
||||
| wkPolling1 | 12 秒 | 取得控制器頁面資料 |
|
||||
| wkPolling2 | 3 秒 | 取得控制器頁面資料 + GC |
|
||||
| wkStatusRefresh | 1 秒 | 狀態刷新 |
|
||||
| wkEventPolling | 18 秒 | 取得事件日誌 |
|
||||
| wkEventPollingForSNMP | 22 秒 | 取得事件並發送 SNMP Trap |
|
||||
|
||||
## Polling 類別
|
||||
|
||||
### wkPolling1 (12秒)
|
||||
|
||||
```java
|
||||
class wkPolling1 extends java.util.TimerTask {
|
||||
public void run() {
|
||||
if (PageDataDB.Polling_Flag) {
|
||||
fMainTmp.Polling(0x01); // iCareOffline = 0x01 (關心離線)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**行為:**
|
||||
- 每 12 秒執行一次
|
||||
- 只在 Polling_Flag = true 時執行
|
||||
- 關心離線狀態 (iCareOffline = 0x01)
|
||||
- 呼叫 Polling(0x01)
|
||||
|
||||
### wkPolling2 (3秒)
|
||||
|
||||
```java
|
||||
class wkPolling2 extends java.util.TimerTask {
|
||||
public void run() {
|
||||
if (PageDataDB.Polling_Flag) {
|
||||
fMainTmp.Polling(0x00); // iCareOffline = 0x00 (不關心離線)
|
||||
}
|
||||
Runtime.getRuntime().gc(); // 垃圾回收
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**行為:**
|
||||
- 每 3 秒執行一次
|
||||
- 不關心離線狀態 (iCareOffline = 0x00)
|
||||
- 呼叫 Polling(0x00)
|
||||
- 執行 GC 垃圾回收
|
||||
|
||||
### wkEventPolling (18秒)
|
||||
|
||||
```java
|
||||
class wkEventPolling extends java.util.TimerTask {
|
||||
public void run() {
|
||||
if (PageDataDB.Polling_Flag) {
|
||||
fMainTmp.GetEvent(); // 取得事件
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**行為:**
|
||||
- 每 18 秒執行一次
|
||||
- 呼叫 GetEvent() 取得事件日誌
|
||||
- 更新 UI 事件列表
|
||||
|
||||
### wkEventPollingForSNMP (22秒)
|
||||
|
||||
```java
|
||||
class wkEventPollingForSNMP extends java.util.TimerTask {
|
||||
public void run() {
|
||||
if (fMainTmp.jtHostList.getSelectedRow() >= 0) {
|
||||
fMainTmp.EventPollingForSNMP(); // 取得事件並發送 SNMP
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**行為:**
|
||||
- 每 22 秒執行一次
|
||||
- 需要有選中的控制器
|
||||
- 呼叫 EventPollingForSNMP() 取得事件並發送 SNMP Trap
|
||||
|
||||
## Polling() 方法
|
||||
|
||||
```java
|
||||
public void Polling(int iCareOffline) {
|
||||
// 遍歷所有已註冊的控制器
|
||||
for (int i = 0; i < jtHostList.getRowCount(); i++) {
|
||||
sHostName = jtHostList.getValueAt(i, iHostList_Host).toString();
|
||||
sIpAddr = jtHostList.getValueAt(i, iHostList_IP).toString();
|
||||
sSN = jtHostList.getValueAt(i, iHostList_SN).toString();
|
||||
|
||||
if (!sSN.equals("")) {
|
||||
// 檢查連線失敗次數
|
||||
if (GetHostCmdFailCnt(sSN) > iMaxCommandFail) {
|
||||
// 設定為離線狀態
|
||||
SetHostStatus(sSN, 0x00);
|
||||
jtHostList.setValueAt(imgRed, i, iHostList_Net);
|
||||
jtHostList.setValueAt("Disconnected", i, iHostList_Status);
|
||||
ClearEventLog(); // 清除事件日誌
|
||||
}
|
||||
|
||||
// 根據參數決定行為
|
||||
if (iCareOffline == 0x01) {
|
||||
// 只關心線上的控制器
|
||||
if (GetHostStatus(sSN) == 0x01) {
|
||||
PageRefresh(sIpAddr, sSN, new Point(0, 0));
|
||||
// 如果是 MAX Storage,刷新備援控制器
|
||||
if (Check_MAX_Storage(sSN)) {
|
||||
PageRefresh(sIpAddr, GetSlaveSerialNumber(sSN), new Point(0, 0));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 刷新所有控制器
|
||||
PageRefresh(sIpAddr, sSN, new Point(0, 0));
|
||||
if (Check_MAX_Storage(sSN)) {
|
||||
PageRefresh(sIpAddr, GetSlaveSerialNumber(sSN), new Point(0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### iCareOffline 參數
|
||||
|
||||
| 值 | 行為 |
|
||||
|----|------|
|
||||
| 0x01 | 只刷新線上 (GetHostStatus == 0x01) 的控制器 |
|
||||
| 0x00 | 刷新所有控制器 (不管離線與否) |
|
||||
|
||||
## GetEvent() 方法
|
||||
|
||||
```java
|
||||
public void GetEvent() {
|
||||
// 取得目前選中的控制器事件
|
||||
GetEvent(sIpAddr, sSN);
|
||||
}
|
||||
|
||||
// PageDataDB.GetEvent()
|
||||
public static void GetEvent(String sIP, String sSN) {
|
||||
// 避免重複執行
|
||||
if (bIsGetEvent) return;
|
||||
bIsGetEvent = true;
|
||||
|
||||
// 建立命令
|
||||
CmdQueue cmdQueue = new CmdQueue();
|
||||
cmdQueue.NewCommand(sIP, sSN, 1, 1, 3);
|
||||
|
||||
// 設定命令內容
|
||||
bCmds[0] = 0xFF; // Phase Code
|
||||
bCmds[1] = 0x00; // Request Code
|
||||
bCmds[2] = 0x00; // Controller No
|
||||
bCmds[3] = 0x02; // InBand Event Cmd Code: Get InBand Event
|
||||
|
||||
// 發送命令
|
||||
issueCmdAgent2 ICA = new issueCmdAgent2();
|
||||
ICA.IssusCmd(sIP, sSN, cmdQueue);
|
||||
|
||||
// 處理回應
|
||||
ReplyTask(cmdQueue, iEventPollingCheckType);
|
||||
|
||||
bIsGetEvent = false;
|
||||
}
|
||||
```
|
||||
|
||||
## EventPollingForSNMP() 方法
|
||||
|
||||
```java
|
||||
public void EventPollingForSNMP() {
|
||||
// 遍歷所有控制器
|
||||
for (int iHost = 0; iHost < iMaxHosts; iHost++) {
|
||||
// 取得控制器資訊
|
||||
sTmpHostName = jtHostList.getValueAt(iHost, iHostList_Host);
|
||||
sTmpIP = jtHostList.getValueAt(iHost, iHostList_IP);
|
||||
sTmpSN = jtHostList.getValueAt(iHost, iHostList_SN);
|
||||
|
||||
// 發送事件查詢命令 (OpCode 0xFF/0x02)
|
||||
CmdQueue cmdQueue = new CmdQueue();
|
||||
cmdQueue.NewCommand(sTmpIP, sTmpSN, 1, 70, 3);
|
||||
|
||||
bCmds[3] = 0x02; // Get InBand Event
|
||||
|
||||
// 發送並處理回應
|
||||
issueCmdAgent2 ICA = new issueCmdAgent2();
|
||||
ICA.IssusCmd(sTmpIP, sTmpSN, cmdQueue);
|
||||
|
||||
// 處理 SNMP 回應
|
||||
ReplyTaskForSNMP(sTmpHostName, sTmpIP, sTmpSN, cmdQueue);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 通訊協定
|
||||
|
||||
### 取得事件 (Get InBand Event)
|
||||
|
||||
```
|
||||
Request:
|
||||
Phase Code: 0xFF
|
||||
Request Code: 0x00
|
||||
Controller: 0x00
|
||||
OpCode: 0x02 (Get InBand Event)
|
||||
|
||||
Response:
|
||||
- 事件資料 (最多 70 bytes)
|
||||
- 包含事件時間、事件 ID、事件描述
|
||||
```
|
||||
|
||||
## Polling 開關
|
||||
|
||||
```java
|
||||
// 選單功能 - Polling On/Off
|
||||
public void jmPollingSwitch_actionPerformed(ActionEvent e) {
|
||||
if (PageDataDB.Polling_Flag) {
|
||||
PageDataDB.Polling_Flag = false;
|
||||
jmPollingSwitch.setText("Polling Off");
|
||||
} else {
|
||||
PageDataDB.Polling_Flag = true;
|
||||
jmPollingSwitch.setText("Polling On");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Polling 狀態判斷
|
||||
|
||||
```
|
||||
主機狀態 (Host Status):
|
||||
0x00 - 離線 (Offline)
|
||||
0x01 - 線上 (Online)
|
||||
0x10 - 更新中 (Updating)
|
||||
|
||||
連線失敗計數:
|
||||
- GetHostCmdFailCnt(sSN) > iMaxCommandFail → 判定離線
|
||||
- 每次命令失敗計數 +1
|
||||
- 每次成功計數歸零
|
||||
```
|
||||
|
||||
## Code Flow
|
||||
|
||||
### 完整 Polling 流程
|
||||
|
||||
```
|
||||
1. Timer 觸發
|
||||
│
|
||||
▼
|
||||
2. 檢查 Polling_Flag
|
||||
│ true → 繼續
|
||||
│ false → 停止
|
||||
▼
|
||||
3. 選擇 Polling 任務
|
||||
│
|
||||
├── wkPolling1 ──► Polling(0x01) ──► PageRefresh() ──► In-Band API
|
||||
│
|
||||
├── wkPolling2 ──► Polling(0x00) ──► PageRefresh() ──► In-Band API
|
||||
│
|
||||
├── wkEventPolling ──► GetEvent() ──► In-Band API
|
||||
│
|
||||
└── wkEventPollingForSNMP ──► EventPollingForSNMP() ──► In-Band API ──► SNMP Trap
|
||||
```
|
||||
|
||||
### 事件處理流程
|
||||
|
||||
```
|
||||
控制器事件發生
|
||||
│
|
||||
▼
|
||||
Polling 偵測到 (Get InBand Event)
|
||||
│
|
||||
▼
|
||||
解析事件內容
|
||||
│
|
||||
├── 更新 UI Event List
|
||||
│
|
||||
├── 發送 SNMP Trap (EventPollingForSNMP)
|
||||
│
|
||||
└── 發送 Email Alert (如果啟用)
|
||||
```
|
||||
|
||||
## 關鍵變數
|
||||
|
||||
```java
|
||||
// PageDataDB.java
|
||||
public static boolean Polling_Flag = true; // Polling 開關
|
||||
public static boolean bIsGetEvent = false; // 防止重複取得事件
|
||||
public static int iMaxCommandFail = 3; // 最大允許連線失敗次數
|
||||
|
||||
// frmMain.java
|
||||
java.util.Timer tmPolling; // Polling 計時器
|
||||
```
|
||||
|
||||
## 效能優化
|
||||
|
||||
1. **分層輪詢**: 不同任務使用不同週期
|
||||
2. **離線加速**: 離線後改用較短週期 (3秒 vs 12秒)
|
||||
3. **事件鎖**: `bIsGetEvent` 防止重複取得
|
||||
4. **GC 排程**: wkPolling2 執行垃圾回收
|
||||
|
||||
## Rust 實作建議
|
||||
|
||||
```rust
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use tokio::time::{interval, Duration};
|
||||
|
||||
pub struct PollingManager {
|
||||
polling_flag: Arc<AtomicBool>,
|
||||
// 其他狀態...
|
||||
}
|
||||
|
||||
impl PollingManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
polling_flag: Arc::new(AtomicBool::new(true)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&self) {
|
||||
let flag = self.polling_flag.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut timer = interval(Duration::from_secs(12));
|
||||
loop {
|
||||
timer.tick().await;
|
||||
if flag.load(Ordering::SeqCst) {
|
||||
self.polling().await;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn stop(&self) {
|
||||
self.polling_flag.store(false, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
async fn polling(&self) {
|
||||
// 取得控制器狀態
|
||||
// 刷新頁面資料
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,340 @@
|
||||
# RAIDGuard X Java GUI 重構為 Rust 分析報告
|
||||
|
||||
## 1. 現有架構總覽
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Java Swing GUI (70,521 行) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ Main Windows │ frmMain (7,397 行) │
|
||||
│ │ frmSettings (2,978 行) │
|
||||
│ │ frmAdvanced (707 行) │
|
||||
├──────────────────────┼──────────────────────────────────────────┤
|
||||
│ Dialogs │ dlgServer (799 行) │
|
||||
│ │ dlgDumpLog (808 行) │
|
||||
│ │ dlgEnvInfo (808 行) │
|
||||
│ │ dlgPasswordCheck │
|
||||
│ │ dlgProgressCheck │
|
||||
├──────────────────────┼──────────────────────────────────────────┤
|
||||
│ Panels │ jpController (3,059 行) │
|
||||
│ │ jpController2 (3,612 行) │
|
||||
├──────────────────────┼──────────────────────────────────────────┤
|
||||
│ RAID Operations │ frmCreateArray (2,171 行) │
|
||||
│ │ frmCreateArray2 (2,278 行) │
|
||||
│ │ frmDeleteArray (834 行) │
|
||||
│ │ frmQuickSetup2 (1,421 行) │
|
||||
├──────────────────────┼──────────────────────────────────────────┤
|
||||
│ Advanced Features │ frmAdvSnapshot (1,430 行) │
|
||||
│ │ frmAdvMirror (1,552 行) │
|
||||
│ │ frmAdvSlicing (1,125 行) │
|
||||
│ │ frmAdvMigrate (956 行) │
|
||||
│ │ frmAdvExpansion (619 行) │
|
||||
│ │ frmAdvLunmap (907 行) │
|
||||
│ │ frmAdvLunMask (913 行) │
|
||||
├──────────────────────┼──────────────────────────────────────────┤
|
||||
│ System │ frmEmail (1,292 行) │
|
||||
│ │ frmAdvMaintain (1,011 行) │
|
||||
├──────────────────────┼──────────────────────────────────────────┤
|
||||
│ Data Layer │ PageDataDB (7,322 行) - 全域資料庫 │
|
||||
│ │ EventPartition (1,422 行) │
|
||||
│ │ Langs (2,468 行) - 多國語言 │
|
||||
├──────────────────────┼──────────────────────────────────────────┤
|
||||
│ Protocol │ SendCmd.java - TCP 通訊 │
|
||||
│ │ ControllerInfoPage │
|
||||
│ │ RAIDInfoPage (675 行) │
|
||||
│ │ DISKInfoPage │
|
||||
│ │ RAIDEnclosureInfoPage │
|
||||
│ │ SnapshotInfoPage │
|
||||
├──────────────────────┼──────────────────────────────────────────┤
|
||||
│ SNMP │ SNMPv1CommunicationInterface (1,229 行) │
|
||||
│ │ SNMPTrapSendTest (653 行) │
|
||||
└──────────────────────┴──────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────────┐
|
||||
│ TCP Port 8922 (In-Band) │
|
||||
│ TCP Port 8722 (Finder) │
|
||||
└───────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────────┐
|
||||
│ GS (GUI Server) - C │
|
||||
│ DtrGuiSrv │
|
||||
└───────────────────────────────┘
|
||||
```
|
||||
|
||||
## 2. 模組分類與重構建議
|
||||
|
||||
### 2.1 核心資料層 (Core Data Layer) - 高優先級
|
||||
|
||||
| 模組 | 行數 | 功能 | Rust 重構建議 |
|
||||
|------|------|------|---------------|
|
||||
| **PageDataDB** | 7,322 | 全域資料庫、快取、SNMP | 使用 `serde` + `rusqlite`,建立 `State` 管理 |
|
||||
| **EventPartition** | 1,422 | 事件日誌讀寫 | 直接移植,使用 `tokio` 異步 I/O |
|
||||
| **Langs** | 2,468 | 多國語言 | 改用 JSON/YAML 語言檔 + `fluent-rs` |
|
||||
|
||||
**Rust 目標結構:**
|
||||
```rust
|
||||
// src/data/mod.rs
|
||||
pub struct AppState {
|
||||
pub controllers: HashMap<String, Controller>,
|
||||
pub raids: HashMap<String, RAID>,
|
||||
pub disks: HashMap<String, Disk>,
|
||||
pub events: Vec<Event>,
|
||||
pub config: Config,
|
||||
}
|
||||
|
||||
// src/i18n/mod.rs
|
||||
pub struct I18n {
|
||||
current_locale: String,
|
||||
translations: HashMap<String, HashMap<String, String>>,
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 通訊層 (Protocol Layer) - 高優先級
|
||||
|
||||
| 模組 | 功能 | Rust 重構建議 |
|
||||
|------|------|---------------|
|
||||
| **SendCmd** | TCP 通訊、指令發送 | 使用 `tokio::net::TcpStream` + 自訂通訊協定 |
|
||||
| **InBandAPI (22 個類別)** | 頁面資料解析 | 移植為 Rust struct derive `Serialize` |
|
||||
|
||||
**Rust 目標結構:**
|
||||
```rust
|
||||
// src/protocol/mod.rs
|
||||
pub struct InBandProtocol {
|
||||
socket: TcpStream,
|
||||
}
|
||||
|
||||
impl InBandProtocol {
|
||||
pub async fn connect(&mut self, addr: &str) -> Result<()>;
|
||||
pub async fn send_cmd(&mut self, opcode: OpCode, data: &[u8]) -> Result<Vec<u8>>;
|
||||
}
|
||||
|
||||
// src/protocol/pages.rs
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ControllerInfoPage {
|
||||
pub serial_number: [u8; 16],
|
||||
pub model_name: [u8; 32],
|
||||
pub firmware_version: [u8; 8],
|
||||
pub status: u8,
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 主視窗 (Main Window) - 中優先級
|
||||
|
||||
| 模組 | 行數 | 功能 |
|
||||
|------|------|------|
|
||||
| **frmMain** | 7,397 | 主視窗、選單、標題列 |
|
||||
| **frmSettings** | 2,978 | 系統設定 |
|
||||
|
||||
**重構建議:** 使用 **Slint** (現有專案已採用)
|
||||
|
||||
```rust
|
||||
// ui/main_window.slint (已存在)
|
||||
export component AppWindow inherits Window {
|
||||
// 現有的 Slint 定義
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 RAID 作業模組 - 中優先級
|
||||
|
||||
| 模組 | 行數 | 功能 |
|
||||
|------|------|------|
|
||||
| frmCreateArray | 2,171 | 建立 RAID |
|
||||
| frmCreateArray2 | 2,278 | 建立 RAID (進階) |
|
||||
| frmDeleteArray | 834 | 刪除 RAID |
|
||||
| frmQuickSetup2 | 1,421 | 快速設定 |
|
||||
|
||||
**Rust 重構建議:**
|
||||
```rust
|
||||
// src/handlers/raid_ops.rs
|
||||
pub async fn create_raid(level: RaidLevel, disks: Vec<u8>) -> Result<()>;
|
||||
pub async fn delete_raid(raid_id: u8) -> Result<()>;
|
||||
pub async fn rebuild_raid(raid_id: u8) -> Result<()>;
|
||||
```
|
||||
|
||||
### 2.5 進階功能模組 - 低優先級
|
||||
|
||||
| 模組 | 行數 | 功能 |
|
||||
|------|------|------|
|
||||
| frmAdvSnapshot | 1,430 | Snapshot 管理 |
|
||||
| frmAdvMirror | 1,552 | Mirror 鏡像 |
|
||||
| frmAdvSlicing | 1,125 | 切片 |
|
||||
| frmAdvMigrate | 956 | 遷移 |
|
||||
| frmAdvExpansion | 619 | 擴展 |
|
||||
| frmAdvLunmap | 907 | LUN 對應 |
|
||||
| frmAdvLunMask | 913 | LUN 遮罩 |
|
||||
|
||||
**重構建議:** 這些功能較少使用,可放在第二階段實作。
|
||||
|
||||
### 2.6 對話框 (Dialogs) - 低優先級
|
||||
|
||||
| 模組 | 行數 | 功能 |
|
||||
|------|------|------|
|
||||
| dlgServer | 799 | 伺服器連線 |
|
||||
| dlgEnvInfo | 808 | 環境資訊 |
|
||||
| dlgDumpLog | 808 | 日誌傾印 |
|
||||
| dlgPasswordCheck | - | 密碼驗證 |
|
||||
| dlgProgressCheck | - | 進度顯示 |
|
||||
|
||||
**重構建議:** 改為 Slint 元件或使用 `rfd` 對話框庫。
|
||||
|
||||
### 2.7 系統模組 - 中優先級
|
||||
|
||||
| 模組 | 行數 | 功能 |
|
||||
|------|------|------|
|
||||
| frmEmail | 1,292 | 郵件通知 |
|
||||
| frmAdvMaintain | 1,011 | 維護功能 |
|
||||
|
||||
**Rust 重構建議:**
|
||||
```rust
|
||||
// src/email/mod.rs
|
||||
pub async fn send_email(config: &EmailConfig, subject: &str, body: &str) -> Result<()>;
|
||||
```
|
||||
|
||||
使用 `lettre` crate 實現 SMTP。
|
||||
|
||||
### 2.8 SNMP 模組 - 低優先級
|
||||
|
||||
| 模組 | 行數 | 功能 |
|
||||
|------|------|------|
|
||||
| SNMPv1CommunicationInterface | 1,229 | SNMP v1/v2c 通訊 |
|
||||
| SNMPTrapSendTest | 653 | Trap 發送測試 |
|
||||
|
||||
**Rust 重構建議:** 使用 `snmp` crate 或自訂實作 (取決於需求)。
|
||||
|
||||
## 3. 重構優先順序
|
||||
|
||||
```
|
||||
Phase 1: 基礎設施 (最優先)
|
||||
├── 通訊層 (SendCmd → InBandProtocol)
|
||||
├── 資料層 (PageDataDB → AppState)
|
||||
└── UI 框架 (Slint 整合)
|
||||
|
||||
Phase 2: 核心功能
|
||||
├── 主視窗 (frmMain → Slint)
|
||||
├── RAID 操作 (Create/Delete/Query)
|
||||
└── 事件處理 (EventPartition)
|
||||
|
||||
Phase 3: 進階功能
|
||||
├── Snapshot / Mirror / Migration
|
||||
├── LUN Map/Mask
|
||||
└── Email 通知
|
||||
|
||||
Phase 4: 優化
|
||||
├── SNMP Trap
|
||||
├── 效能調優
|
||||
└── 單元測試
|
||||
```
|
||||
|
||||
## 4. Rust 專案結構建議
|
||||
|
||||
```
|
||||
raidguard_x_gui/
|
||||
├── Cargo.toml
|
||||
├── build.rs
|
||||
├── src/
|
||||
│ ├── main.rs # 入口點
|
||||
│ ├── lib.rs # 庫入口
|
||||
│ │
|
||||
│ ├── protocol/ # 通訊協定
|
||||
│ │ ├── mod.rs
|
||||
│ │ ├── client.rs # TCP 客戶端
|
||||
│ │ ├── messages.rs # 訊息定義
|
||||
│ │ └── pages.rs # 頁面資料結構
|
||||
│ │
|
||||
│ ├── data/ # 資料層
|
||||
│ │ ├── mod.rs
|
||||
│ │ ├── state.rs # 全域狀態
|
||||
│ │ ├── cache.rs # 快取
|
||||
│ │ └── db.rs # SQLite 儲存
|
||||
│ │
|
||||
│ ├── handlers/ # 業務邏輯
|
||||
│ │ ├── mod.rs
|
||||
│ │ ├── raid.rs # RAID 操作
|
||||
│ │ ├── disk.rs # 磁碟操作
|
||||
│ │ ├── controller.rs # 控制器操作
|
||||
│ │ └── event.rs # 事件處理
|
||||
│ │
|
||||
│ ├── email/ # 郵件通知
|
||||
│ │ └── mod.rs
|
||||
│ │
|
||||
│ ├── snmp/ # SNMP (可選)
|
||||
│ │ └── mod.rs
|
||||
│ │
|
||||
│ └── ui/ # UI 整合
|
||||
│ └── mod.rs
|
||||
│
|
||||
└── ui/ # Slint UI 檔案
|
||||
├── main_window.slint
|
||||
├── components/
|
||||
└── styles/
|
||||
```
|
||||
|
||||
## 5. 依賴套件建議
|
||||
|
||||
| 功能 | Rust Crate | 備註 |
|
||||
|------|------------|------|
|
||||
| UI Framework | `slint` | 現已採用 |
|
||||
| 非同步執行 | `tokio` | 現已採用 |
|
||||
| 序列/反序列化 | `serde`, `serde_json` | 現已採用 |
|
||||
| TCP 通訊 | `tokio::net` | 標準庫 |
|
||||
| 資料庫 | `rusqlite` | SQLite |
|
||||
| 日期時間 | `chrono` | 現已採用 |
|
||||
| 日誌 | `tracing` | 現已採用 |
|
||||
| SMTP | `lettre` | 郵件發送 |
|
||||
| 指令列解析 | `clap` | 選項解析 |
|
||||
| 錯誤處理 | `anyhow`, `thiserror` | 現已採用 |
|
||||
|
||||
## 6. 現有 Rust 程式碼整合
|
||||
|
||||
現有 Rust 專案 (`raidguard_x_gui_server/`) 可作為基礎:
|
||||
|
||||
```rust
|
||||
// 現有結構
|
||||
raidguard_x_gui_server/
|
||||
├── src/
|
||||
│ ├── main.rs ✅ 入口點
|
||||
│ ├── server.rs ✅ TCP 伺服器
|
||||
│ ├── protocol/messages.rs ✅ 訊息定義
|
||||
│ ├── mock/data.rs ✅ 模擬資料
|
||||
│ └── handlers/ ✅ 業務處理
|
||||
└── Cargo.toml
|
||||
```
|
||||
|
||||
**整合策略:**
|
||||
1. 將 `raidguard_x_gui_server` 改為 `raidguard_x_gui` (合併 client + server)
|
||||
2. 新增 Slint UI 整合 (`ui/` 目錄 → Slint 編譯)
|
||||
3. 新增 `protocol/client.rs` 實現 TCP 客戶端
|
||||
4. 新增 `data/state.rs` 實現全域狀態
|
||||
|
||||
## 7. 風險與挑戰
|
||||
|
||||
| 風險 | 緩解措施 |
|
||||
|------|----------|
|
||||
| Swing → Slint 遷移複雜度 | 分階段進行,先實現核心 UI |
|
||||
| 執行緒模型差異 | 使用 `tokio` 模擬 Swing EDT 概念 |
|
||||
| 效能考量 | Rust效能通常優於 Java,但仍需優化 |
|
||||
| 測試覆蓋率 | 建立完整單元測試與整合測試 |
|
||||
| Java/Swing 依賴 | 保留 Java 版本,逐步替換 |
|
||||
|
||||
## 8. 結論
|
||||
|
||||
### 重構可行性: ✅ 高
|
||||
|
||||
- **總行數**: 70,521 行 Java → 預估 30,000 行 Rust
|
||||
- **預期效能提升**: 2-5x
|
||||
- **維護性提升**: 顯著改善 (型別安全、記憶體安全)
|
||||
|
||||
### 建議策略
|
||||
|
||||
1. **先完成 Phase 1-2** (通訊 + 資料 + 核心 UI)
|
||||
2. **保留 Java 版本** 同步運行,逐步替換
|
||||
3. **使用 Mock Driver** 進行離線測試
|
||||
4. **建立 CI/CD** 確保重構正確性
|
||||
|
||||
---
|
||||
|
||||
*Generated: 2026-03-29*
|
||||
*Based on: RAIDGuard X Java GUI (v3.8.0)*
|
||||
@@ -0,0 +1,156 @@
|
||||
# Update System Code (Firmware Update) 實現分析
|
||||
|
||||
## 流程架構
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ jmUpdateFirmware_actionPerformed() │
|
||||
│ (密碼驗證) │
|
||||
└──────────────────────────┬──────────────────────────────────┘
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ UpdateFirmware(iCmdCode, microcode, enclosureID, type) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 1. 開啟檔案選擇器 (JFileChooser) │
|
||||
│ 2. 檢查檔案有效性 │
|
||||
│ 3. 建立 CmdQueue 命令 │
|
||||
│ 4. 讀取 FW 檔案 │
|
||||
│ 5. 顯示進度對話框 (dlgProgressCheck) │
|
||||
│ 6. 發送 FW 到控制器 │
|
||||
└──────────────────────────┬──────────────────────────────────┘
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 進度監控 (dlgProgressCheck) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Firmware 類型
|
||||
|
||||
| iCmdCode | microcode | enclosureID | 類型 | 說明 |
|
||||
|----------|-----------|-------------|------|------|
|
||||
| 0x94 | 0xFF | 0xFF | System Code | 主系統韌體更新 |
|
||||
| 0x95 | 0xFF | 0xFF | Boot Code | 啟動韌體更新 |
|
||||
| 0x96 | 0x00 | 0xFF | BIOS/EFI | BIOS/EFI 更新 |
|
||||
| 0x96 | 0x01 | [ID] | Expander Code | Expander 韌體更新 |
|
||||
|
||||
## 通訊協定
|
||||
|
||||
### Command Queue (CmdQueue)
|
||||
|
||||
```java
|
||||
// 建立命令
|
||||
int iRow = CmdQueue.NewCommand(
|
||||
IP, // 控制器 IP
|
||||
SN, // 序號
|
||||
4200, // Command Len (min fw size)
|
||||
1, // Reply Len
|
||||
0x01 // Req Type: InBand Command
|
||||
);
|
||||
|
||||
// CDB 結構
|
||||
bCmds[0] = 0x00; // Phase Code
|
||||
bCmds[1] = 0x00; // Request Code
|
||||
bCmds[2] = 0x00; // Controller No
|
||||
bCmds[3] = (byte) iCmdCode; // 0x94/0x95/0x96
|
||||
bCmds[4] = 0x04; // Length (default)
|
||||
bCmds[5-8] = FW Length (4 bytes, little-endian)
|
||||
bCmds[9] = microcode;
|
||||
bCmds[10] = enclosureID (if microcode == 0x01)
|
||||
|
||||
// FW 資料 (從偏移量 9 或 10 開始)
|
||||
bCmds[9/10+] = firmware data
|
||||
```
|
||||
|
||||
### CDB 參數說明
|
||||
|
||||
| Offset | 值 | 說明 |
|
||||
|--------|-----|------|
|
||||
| 0 | 0x00 | Phase Code |
|
||||
| 1 | 0x00 | Request Code |
|
||||
| 2 | 0x00 | Controller No |
|
||||
| 3 | 0x94/0x95/0x96 | Command Code |
|
||||
| 4 | 0x04-0x06 | Length |
|
||||
| 5-8 | FW Length | 韌體大小 (4 bytes) |
|
||||
| 9 | microcode | 微碼類型 |
|
||||
| 10 | enclosureID | Enclosure ID (選用) |
|
||||
| 11+ | firmware data | 韌體內容 |
|
||||
|
||||
## 檔案驗證
|
||||
|
||||
```java
|
||||
// 檢查 1: 檔案存在
|
||||
if (!f.exists()) {
|
||||
showError("File does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
// 檢查 2: 檔案大小 (< 4MB)
|
||||
if (f.length() > 4048000) {
|
||||
showWarning("File too large (>4MB)");
|
||||
return;
|
||||
}
|
||||
|
||||
// 檢查 3: 主機狀態 (必須在線)
|
||||
if (GetHostStatus(sSN) != 0x01) {
|
||||
showWarning("Controller not online");
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
## Code Flow
|
||||
|
||||
### 1. Menu 點擊
|
||||
```
|
||||
jmUpdateFirmware (MenuItem)
|
||||
→ jmUpdateFirmware_actionPerformed()
|
||||
→ PwdCheck() 密碼驗證
|
||||
→ UpdateFirmware(0x94, 0xff, 0xff, "System Code")
|
||||
```
|
||||
|
||||
### 2. Firmware 選擇
|
||||
```
|
||||
UpdateFirmware()
|
||||
→ JFileChooser.showOpenDialog() 選擇 .bin 檔
|
||||
→ 檢查檔案存在、大小限制
|
||||
→ 讀取檔案到 byte array
|
||||
→ CmdQueue.NewCommand() 建立命令
|
||||
```
|
||||
|
||||
### 3. 發送 FW
|
||||
```
|
||||
→ dlgProgressCheck 顯示進度
|
||||
→ 設定主機狀態為 updating (0x10)
|
||||
→ 透過 In-Band API 發送到控制器
|
||||
→ UpdateFirmwareSlave() 更新備援控制器 (如有)
|
||||
```
|
||||
|
||||
## 關鍵類
|
||||
|
||||
| 類別 | 檔案 | 功能 |
|
||||
|------|------|------|
|
||||
| `frmMain` | frmMain.java:4077 | 入口,密碼驗證 |
|
||||
| `UpdateFirmware` | frmMain.java:3663 | 主 FW 更新邏輯 |
|
||||
| `UpdateFirmwareSlave` | frmMain.java:3866 | 備援 FW 更新 |
|
||||
| `dlgProgressCheck` | dlgProgressCheck.java | 進度顯示對話框 |
|
||||
| `CmdQueue` | CmdQueue.java | 命令排程 |
|
||||
| `SendCmd` | InBandAPI/SendCmd.java | TCP 通訊底層 |
|
||||
|
||||
## 錯誤訊息 (Langs)
|
||||
|
||||
| Key | 訊息 |
|
||||
|-----|------|
|
||||
| Main_UpdateFirmwareMsg1 | No file selected |
|
||||
| Main_UpdateFirmwareMsg2 | Please connect to controller first |
|
||||
| Main_UpdateFirmwareMsg3_1 | File not found: |
|
||||
| Main_UpdateFirmwareMsg3_2 | |
|
||||
| Main_UpdateFirmwareMsg4 | File size over 4M, cannot update! |
|
||||
|
||||
## 狀態更新
|
||||
|
||||
```java
|
||||
// 更新前
|
||||
PageDataDB.SetHostStatus(sSN, 0x10); // 0x10 = Updating
|
||||
|
||||
// UI 顯示
|
||||
jtHostList.setValueAt("System Code updating.", row, StatusColumn);
|
||||
```
|
||||
@@ -0,0 +1,327 @@
|
||||
# GS Event 處理機制分析
|
||||
|
||||
## 概述
|
||||
|
||||
GS (DtrGuiSrv) 的事件處理機制負責:
|
||||
- 定期輪詢 RAID 控制器的事件
|
||||
- 儲存事件到記憶體佇列
|
||||
- 記錄事件到檔案
|
||||
- 觸發 Email / SNMP 通知
|
||||
|
||||
## 架構流程
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ GS Event 處理架構 │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ PollingCtlrEvent (Thread) │ │
|
||||
│ │ ┌─────────────────────────────────────────────────────┐ │ │
|
||||
│ │ │ while(1) { │ │ │
|
||||
│ │ │ for (每個控制器) { │ │ │
|
||||
│ │ │ GetCtlrEvent() ──► 取得 RAID 事件 │ │ │
|
||||
│ │ │ │ │ │ │
|
||||
│ │ │ ▼ │ │ │
|
||||
│ │ │ SaveInBandEvent() ──► 儲存到記憶體佇列 │ │ │
|
||||
│ │ │ │ │ │ │
|
||||
│ │ │ ▼ │ │ │
|
||||
│ │ │ RecEventToFile() ──► 寫入 Event Log 檔案 │ │ │
|
||||
│ │ │ } │ │ │
|
||||
│ │ │ sleep(30秒) │ │ │
|
||||
│ │ │ } │ │ │
|
||||
│ │ └─────────────────────────────────────────────────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ HandleInBandEventReq (GC 請求) │ │
|
||||
│ │ - 0x01: 取得單一事件 (Remove_InBand_Event) │ │
|
||||
│ │ - 0x02: 取得所有事件 │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ 事件處理動作 │ │
|
||||
│ │ - Email 通知 (IssueMailOut) │ │
|
||||
│ │ - SNMP Trap 發送 │ │
|
||||
│ │ - UI 更新 │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 主要函數
|
||||
|
||||
### 1. PollingCtlrEvent - 事件輪詢執行緒
|
||||
|
||||
```c
|
||||
// cPollingEvent.c:1397
|
||||
UCHAR PollingCtlrEvent(pid_t pid2) {
|
||||
while(1) {
|
||||
for(i=0; i<MAX_DTR_IN_HOST-1; i++) {
|
||||
pCtrlInfo = pGetgloDTRInfo(i);
|
||||
|
||||
// 只處理線上的控制器
|
||||
if(pCtrlInfo->bStatus != CTLR_STATUS_ALIVE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 取得控制器事件
|
||||
bStatus = GetCtlrEvent(i, &event_info);
|
||||
|
||||
// 處理每個事件
|
||||
while(event_info.bStatus) {
|
||||
// FW 更新事件處理
|
||||
if(event_info.bEventBuf[0] == FW_FLUSH_EVENT) {
|
||||
// 設定 FW 狀態
|
||||
}
|
||||
|
||||
// 儲存事件
|
||||
SaveInBandEvent(i, &event_info);
|
||||
|
||||
// 寫入檔案
|
||||
RecEventToFile(i, &event_info);
|
||||
}
|
||||
}
|
||||
|
||||
sleep(MAX_EVENT_POLLING_THREAD_TIME_DELAY); // 30秒
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. SaveInBandEvent - 儲存事件到記憶體
|
||||
|
||||
```c
|
||||
// cPollingEvent.c:1095
|
||||
UCHAR SaveInBandEvent(UCHAR bCtlrNo, PEVENT_INFO pEvent_info) {
|
||||
// 取得控制器的事件佇列
|
||||
sCtlrEvent = pGetEventFreeQ(bCtlrNo);
|
||||
|
||||
// 從空閒佇列取得一個 entry
|
||||
CurQ = sCtlrEvent->InBandFreeQ;
|
||||
|
||||
// 複製事件資料
|
||||
memcpy(CurQ->bEventBuf, pEvent_info->bEventBuf, MAX_EDB_SZ);
|
||||
|
||||
// 標記為有效
|
||||
CurQ->bStatus = TRUE;
|
||||
|
||||
return SYSOK;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. RecEventToFile - 寫入事件檔案
|
||||
|
||||
```c
|
||||
// cPollingEvent.c:1145
|
||||
void RecEventToFile(UCHAR bCtlrNo, PEVENT_INFO pEvent_info) {
|
||||
// 取得時間
|
||||
GetSystemDateTime(DateTimeBuf);
|
||||
|
||||
// 解析事件
|
||||
ParseEvent2(pEvent_info->bEventBuf, EventStrBuf, EventMailBuf);
|
||||
|
||||
// 寫入 Event Log 檔案
|
||||
pFile_EM = fopen(FileNameBuf, "a+");
|
||||
fprintf(pFile_EM, "%s\t%s\n", DateTimeBuf, EventStrBuf);
|
||||
fclose(pFile_EM);
|
||||
|
||||
// 建立 Email 通知檔案
|
||||
// (供 Email 發送服務讀取)
|
||||
}
|
||||
```
|
||||
|
||||
### 4. HandleInBandEventReq - GC 請求處理
|
||||
|
||||
```c
|
||||
// cPollingEvent.c:1478
|
||||
UCHAR HandleInBandEventReq(PNET_REQ_PACKAGE pReqPackage, SHORT iCtlrIndex) {
|
||||
switch(pReqPackage->pInBuffer[INBAND_EVENT_REQ_F_CMD_CODE]) {
|
||||
case 0x01: // 取得單一事件
|
||||
bStatus = Remove_InBand_Event(iCtlrIndex, &EventInfo);
|
||||
// 回傳事件資料
|
||||
break;
|
||||
|
||||
case 0x02: // 取得所有事件
|
||||
pEMH = pGetEventMsgHeader(iCtlrIndex);
|
||||
// 回傳所有事件 + 事件數量
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 事件結構
|
||||
|
||||
### EVENT_INFO
|
||||
|
||||
```c
|
||||
typedef struct _EVENT_INFO {
|
||||
UCHAR bStatus; // 事件狀態 (TRUE=有事件)
|
||||
UCHAR bEventBuf[MAX_EDB_SZ]; // 事件資料 (512 bytes)
|
||||
} EVENT_INFO, *PEVENT_INFO;
|
||||
```
|
||||
|
||||
### 事件類型
|
||||
|
||||
| Event Code | 說明 |
|
||||
|-----------|------|
|
||||
| FW_FLASH_EVENT | 韌體更新事件 |
|
||||
| 0x00-0xFF | 其他事件 |
|
||||
|
||||
## 事件命令碼
|
||||
|
||||
| Command | 值 | 說明 |
|
||||
|---------|-----|------|
|
||||
| Get InBand Event | 0x01 | 取得並移除一個事件 |
|
||||
| Get All InBand Event | 0x02 | 取得所有事件 |
|
||||
|
||||
## 通訊協議
|
||||
|
||||
### GC 請求
|
||||
|
||||
```
|
||||
Offset 0: Phase Code (0xFF)
|
||||
Offset 1: Request Code (0x00)
|
||||
Offset 2: Controller No
|
||||
Offset 3: Event Command (0x01/0x02)
|
||||
```
|
||||
|
||||
### GS 回應
|
||||
|
||||
```
|
||||
Offset 0-2: Phase/Request/Controller
|
||||
Offset 3: Status (0x50=Success)
|
||||
Offset 4: InBand Event Status
|
||||
Offset 5-8: Event Count (for 0x02)
|
||||
Offset 9+: Event Data
|
||||
```
|
||||
|
||||
## 資料結構
|
||||
|
||||
### 事件佇列
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ InBand Event Queue │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ ┌─────────────┐ │
|
||||
│ │ Free Q │ ──► Event Entry 1 ──► Event Entry 2 │
|
||||
│ └─────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐│
|
||||
│ │ Event Q Head ─────────────────────────► Tail ││
|
||||
│ │ [Event1] -> [Event2] -> [Event3] -> ... ││
|
||||
│ └─────────────────────────────────────────────────────────┘│
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### MAX_EVENT_NO
|
||||
|
||||
```
|
||||
#define MAX_EVENT_NO 512 // 每個控制器最大事件數
|
||||
#define MAX_EDB_SZ 512 // 每個事件大小
|
||||
```
|
||||
|
||||
## 輪詢週期
|
||||
|
||||
```c
|
||||
// cPollingEvent.c
|
||||
#define MAX_EVENT_POLLING_THREAD_TIME_DELAY 30 // 30 秒
|
||||
|
||||
sleep(MAX_EVENT_POLLING_THREAD_TIME_DELAY);
|
||||
```
|
||||
|
||||
## Email 通知
|
||||
|
||||
```c
|
||||
// 事件觸發 Email
|
||||
void RecEventToFile(UCHAR bCtlrNo, PEVENT_INFO pEvent_info) {
|
||||
// 解析事件
|
||||
ParseEvent2(pEvent_info->bEventBuf, EventStrBuf, EventMailBuf);
|
||||
|
||||
// 建立 Email 通知檔案
|
||||
// IssueMailOut(bCtlrNo, ...);
|
||||
}
|
||||
```
|
||||
|
||||
## GC 端對應
|
||||
|
||||
### Java 請求
|
||||
|
||||
```java
|
||||
// frmMain.java - EventPollingForSNMP()
|
||||
|
||||
// 發送請求
|
||||
bCmds[0] = (byte) 0xFF; // Phase Code
|
||||
bCmds[1] = (byte) 0x00; // Request Code
|
||||
bCmds[2] = (byte) 0x00; // Controller No
|
||||
bCmds[3] = (byte) 0x02; // Get InBand Event
|
||||
```
|
||||
|
||||
## 關鍵變數
|
||||
|
||||
```c
|
||||
// 全域變數
|
||||
PCTLR_EVENT glo_sCtlrEvent[MAX_DTR_IN_HOST]; // 每個控制器的事件
|
||||
PINBAND_EVENT_Q glo_sCtlrEventEntry[MAX_EVENT_NO]; // 事件 entry
|
||||
|
||||
UCHAR bPopEventMsg = FALSE; // 是否取出事件
|
||||
UCHAR bIssueMail = FALSE; // 是否發送郵件
|
||||
```
|
||||
|
||||
## 處理流程圖
|
||||
|
||||
```
|
||||
RAID Controller
|
||||
│
|
||||
│ In-Band Protocol
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ GetCtlrEvent() │ 取得事件
|
||||
└──────────┬──────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ event_info.bStatus │ = TRUE?
|
||||
└──────────┬──────────┘
|
||||
Yes │
|
||||
▼
|
||||
┌─────┴─────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
SaveInBand RecEventToFile
|
||||
│ │
|
||||
│ ├──► Event Log File
|
||||
│ │
|
||||
│ └──► Email File (for IssueMailOut)
|
||||
│
|
||||
▼
|
||||
Event Queue (Memory)
|
||||
│
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ HandleInBandEventReq│ GC 讀取事件
|
||||
│ (0x01 / 0x02) │
|
||||
└──────────┬──────────┘
|
||||
│
|
||||
▼
|
||||
GC Client
|
||||
```
|
||||
|
||||
## 常見錯誤碼
|
||||
|
||||
| 錯誤碼 | 說明 |
|
||||
|--------|------|
|
||||
| SYSOK | 成功 |
|
||||
| SYSFAIL_EVENT_EMPTY | 無事件 |
|
||||
| SYSFAIL_EVENT_NO_FREE_Q | 事件佇列已滿 |
|
||||
| SYSFAIL_EVENT_INVALID | 無效事件 |
|
||||
|
||||
## 實作重點
|
||||
|
||||
1. **記憶體佇列**: 使用 linked list 儲存事件
|
||||
2. **線程安全**: 使用 Semaphore 保護共享資源
|
||||
3. **事件去重**: 每個事件只處理一次
|
||||
4. **檔案記錄**: 所有事件寫入 Event Log
|
||||
5. **異步通知**: Email/SNMP 異步觸發
|
||||
@@ -0,0 +1,281 @@
|
||||
# GS (DtrGuiSrv) <--> GC (GUI Client) 互動分析
|
||||
|
||||
## 系統架構
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ RAIDGuard X 系統架構 │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ GC (GUI) │ │ GS (Server) │ │
|
||||
│ │ RAIDGuard X │◄────────►│ DtrGuiSrv │ │
|
||||
│ │ (Java Swing) │ Port │ (C Program) │ │
|
||||
│ │ │ 8922 │ │ │
|
||||
│ └─────────────────┘ └────────┬────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ In-Band API │ │
|
||||
│ │ (RAID Controller)│ │
|
||||
│ │ Port 8922 │ │
|
||||
│ └─────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 通訊流程
|
||||
|
||||
### 1. 連線建立
|
||||
|
||||
```
|
||||
GC (Client) GS (Server)
|
||||
│ │
|
||||
│──── connect(8922) ─────────────────►│
|
||||
│◄──── accept() ─────────────────────│
|
||||
│ │
|
||||
│──── Phase1: Length ────────────────►│
|
||||
│◄──── Phase1: Ack ───────────────────│
|
||||
│ │
|
||||
│──── Phase2: Data ──────────────────►│
|
||||
│◄──── Phase2: Response ──────────────│
|
||||
│ │
|
||||
│ ... (重複) ... │
|
||||
│ │
|
||||
│──── close() ───────────────────────►│
|
||||
```
|
||||
|
||||
### 2. 傳輸協議 (TCP Port 8922)
|
||||
|
||||
GS 作為 TCP Server,監聽 Port 8922。
|
||||
|
||||
```c
|
||||
// cNetWork.c
|
||||
#define DTR_GUI_PORT 8922
|
||||
|
||||
SHORT InitialSrvSocket(ULONG SerPortNum, ULONG ClientConnectionAllow) {
|
||||
server_sockfd = socket(AF_INET, SOCK_STREAM, 0x00);
|
||||
bind(server_sockfd, ...);
|
||||
listen(server_sockfd, ClientConnectionAllow);
|
||||
return server_sockfd;
|
||||
}
|
||||
```
|
||||
|
||||
## 請求類型
|
||||
|
||||
| Request Type | 值 | 說明 |
|
||||
|-------------|-----|------|
|
||||
| REQ_INBAND_COMMAND | 0x01 | In-Band 命令 (轉發到 RAID 控制器) |
|
||||
| REQ_SERVER_CONFIG | 0x02 | 伺服器設定 (SMTP, Email 等) |
|
||||
|
||||
### Request 格式
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Request Package │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Byte 0 │ Byte 1 │ Byte 2-3 │ Byte 4+ │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Req Type │ Phase Code │ Length │ Data │
|
||||
│ (0x01/0x02)│ │ (LE) │ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Response 格式
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Response Package │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Byte 0 │ Byte 1 │ Byte 2 │ Byte 3+ │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Phase Code │ Req Code │ Ctrl No │ Data / Error Code │
|
||||
│ 0x50 │ │ │ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 處理流程
|
||||
|
||||
### GS 端處理 (cNetWork.c)
|
||||
|
||||
```c
|
||||
// 主迴圈
|
||||
while(1) {
|
||||
client_sockfd = accept(server_sockfd, ...);
|
||||
|
||||
// 接收請求
|
||||
GUITrans(client_sockfd, pInBuffer, ...); // Phase 1
|
||||
GUITrans(client_sockfd, pInBuffer, ...); // Phase 2
|
||||
|
||||
// 解析請求類型
|
||||
switch(pInBuffer[0]) {
|
||||
case REQ_INBAND_COMMAND: // 0x01
|
||||
// 取得 Controller ID
|
||||
iCtlrID = SNtoCtlrID(sSN);
|
||||
|
||||
// 轉發到 RAID 控制器
|
||||
bStatus = HandleInBandReq(&ReqPackage, iCtlrID, &usInBandErrCode);
|
||||
break;
|
||||
|
||||
case REQ_SERVER_CONFIG: // 0x02
|
||||
// 處理伺服器設定
|
||||
bStatus = HandleSrvConfigReq(&ReqPackage);
|
||||
break;
|
||||
}
|
||||
|
||||
// 發送回應
|
||||
ServerGUITrans(&ReqPackage);
|
||||
}
|
||||
```
|
||||
|
||||
## GC 端請求範例
|
||||
|
||||
### 1. 取得頁面資料 (GET_PAGE)
|
||||
|
||||
```java
|
||||
// InBandAPI/SendCmd.java
|
||||
|
||||
// 組建命令
|
||||
byte[] bCmds = new byte[...];
|
||||
bCmds[0] = 0x00; // Phase Code
|
||||
bCmds[1] = 0x00; // Request Code
|
||||
bCmds[2] = 0x00; // Controller No
|
||||
bCmds[3] = 0x01; // OpCode: GET_PAGE
|
||||
bCmds[4] = 0x03; // Length
|
||||
bCmds[5] = pageCode; // Page Code (0x00, 0x01, 0x07, etc.)
|
||||
```
|
||||
|
||||
### 2. 發送密碼 (SEND_PASSWORD)
|
||||
|
||||
```java
|
||||
bCmds[3] = 0x1D; // OpCode: SEND_PASSWORD
|
||||
bCmds[4] = 0x08; // Length (8 bytes)
|
||||
// bCmds[5-12] = password bytes
|
||||
```
|
||||
|
||||
### 3. 取得事件 (Get InBand Event)
|
||||
|
||||
```java
|
||||
bCmds[0] = 0xFF; // Phase Code
|
||||
bCmds[1] = 0x00; // Request Code
|
||||
bCmds[2] = 0x00; // Controller No
|
||||
bCmds[3] = 0x02; // OpCode: Get Event
|
||||
```
|
||||
|
||||
## 資料傳輸函數
|
||||
|
||||
### GS 端 (cNetWork.c)
|
||||
|
||||
```c
|
||||
UCHAR GUITrans(SHORT sd, PUCHAR pInBuffer, ULONG InBufferLength, SHORT flg_trans) {
|
||||
while(LastByte) {
|
||||
if(flg_trans == SERVER_READ) {
|
||||
ByteSend = recv(sd, pBaseAddr, LastByte, 0x00);
|
||||
} else if(flg_trans == SERVER_WRITE) {
|
||||
ByteSend = send(sd, pBaseAddr, LastByte, 0x00);
|
||||
}
|
||||
LastByte -= ByteSend;
|
||||
pBaseAddr += ByteSend;
|
||||
}
|
||||
return SYSOK;
|
||||
}
|
||||
|
||||
UCHAR ServerGUITrans(PNET_REQ_PACKAGE pReqPackage) {
|
||||
// Phase 1: 傳輸長度
|
||||
if(pReqPackage->direction == SERVER_READ) {
|
||||
// 接收長度
|
||||
} else {
|
||||
// 發送長度
|
||||
Phase1Buf[0] = TRANS_PHASE1;
|
||||
Phase1Buf[1] = (Length & 0xFF);
|
||||
Phase1Buf[2] = ((Length >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
// Phase 2: 傳輸資料
|
||||
pReqPackage->pOutBuffer[0] = TRANS_PHASE2;
|
||||
GUITrans(..., SERVER_WRITE);
|
||||
|
||||
return SYSOK;
|
||||
}
|
||||
```
|
||||
|
||||
## 錯誤處理
|
||||
|
||||
### 錯誤回應格式
|
||||
|
||||
```
|
||||
Byte 0: Phase Code
|
||||
Byte 1: Request Code
|
||||
Byte 2: Controller Number
|
||||
Byte 3: Error Code
|
||||
Byte 4-5: InBand Error Code (LSB ~ MSB)
|
||||
```
|
||||
|
||||
### 常見錯誤碼
|
||||
|
||||
| 錯誤碼 | 說明 |
|
||||
|--------|------|
|
||||
| 0x50 | 成功 (Success) |
|
||||
| 0x4x | 失敗 (Fail) |
|
||||
| SYSFAIL_SERSVE_RESEND_FAIL | 傳輸失敗 |
|
||||
| SYSFAIL_INBAND_SN_NOT_MATCH | SN 不匹配 |
|
||||
|
||||
## 關鍵檔案對應
|
||||
|
||||
### GS 端 (C)
|
||||
|
||||
| 檔案 | 功能 |
|
||||
|------|------|
|
||||
| cNetWork.c | TCP 網路通訊 |
|
||||
| cInBand.c | In-Band API 處理 |
|
||||
| cPollingEvent.c | 事件輪詢 |
|
||||
| cMail.c | Email 發送 |
|
||||
| cShareMem.c | 共享記憶體 |
|
||||
|
||||
### GC 端 (Java)
|
||||
|
||||
| 檔案 | 功能 |
|
||||
|------|------|
|
||||
| SendCmd.java | TCP 通訊封裝 |
|
||||
| CmdQueue.java | 命令排程 |
|
||||
| PageDataDB.java | 資料儲存 |
|
||||
| frmMain.java | 主介面 |
|
||||
|
||||
## 通訊序列圖
|
||||
|
||||
```
|
||||
GC GS RAID Controller
|
||||
│ │ │
|
||||
│── TCP Connect(8922) ──►│ │
|
||||
│◄── Accept ─────────────│ │
|
||||
│ │ │
|
||||
│── REQ_INBAND_COMMAND ─►│ │
|
||||
│ (GET_PAGE) │ │
|
||||
│ │── InBand Protocol ───────►│
|
||||
│ │◄── Page Data ───────────│
|
||||
│◄─ Page Data ──────────│ │
|
||||
│ │ │
|
||||
│── REQ_INBAND_COMMAND ─►│ │
|
||||
│ (SEND_PASSWORD) │ │
|
||||
│ │── InBand Protocol ───────►│
|
||||
│ │◄── Status ──────────────│
|
||||
│◄─ Status ─────────────│ │
|
||||
│ │ │
|
||||
│── TCP Close ──────────│ │
|
||||
```
|
||||
|
||||
## 連接埠
|
||||
|
||||
| Port | 用途 |
|
||||
|------|------|
|
||||
| 8922 | GC ↔ GS 主要通訊 |
|
||||
| 8922 | GS ↔ RAID Controller |
|
||||
| 162 | SNMP Trap (可選) |
|
||||
|
||||
## 實作重點
|
||||
|
||||
1. **雙向通訊**: GS 同時連接 GC (客戶端) 和 RAID Controller (硬體)
|
||||
2. **協議轉發**: GS 將 GC 的請求轉發給 RAID Controller
|
||||
3. **錯誤回傳**: 錯誤從 RAID Controller → GS → GC
|
||||
4. **長連接**: 使用 TCP keep-alive 維持連線
|
||||
5. **共享記憶體**: GS 與本機 RAID Driver 共享資料
|
||||
@@ -0,0 +1,520 @@
|
||||
# GS Mock Mode 模擬模式分析
|
||||
|
||||
## 概述
|
||||
|
||||
為 GS (DtrGuiSrv) 新增 Mock 模擬模式,無需 RAID Driver 即可運行,用於:
|
||||
- 開發測試
|
||||
- CI/CD 自動化測試
|
||||
- 離線 Demo
|
||||
- 功能驗證
|
||||
|
||||
## 需要模擬的關鍵函數
|
||||
|
||||
### 1. Driver I/O 層
|
||||
|
||||
| 函數 | 用途 | 模擬難度 |
|
||||
|------|------|----------|
|
||||
| `DevIoControl()` | I/O Control 與 Driver 通訊 | 高 |
|
||||
| `QuaryDTR()` | 查詢 RAID 控制器資訊 | 中 |
|
||||
| `GetCtlrEvent()` | 取得控制器事件 | 中 |
|
||||
|
||||
### 2. 共享記憶體層
|
||||
|
||||
| 函數 | 用途 | 模擬難度 |
|
||||
|------|------|----------|
|
||||
| `InitialSHMForAllCtlr()` | 初始化共享記憶體 | 高 |
|
||||
| 相關 SHM 函數 | 共享記憶體操作 | 高 |
|
||||
|
||||
## 模擬模式架構
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ GS Mock Mode 架構 │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ GS (DtrGuiSrv) │ │
|
||||
│ │ │ │
|
||||
│ │ ┌─────────────────────────────────────────────────────┐ │ │
|
||||
│ │ │ Mock Mode 開關 │ │ │
|
||||
│ │ │ #define ENABLE_MOCK_MODE │ │ │
|
||||
│ │ └─────────────────────────────────────────────────────┘ │ │
|
||||
│ │ │ │ │
|
||||
│ │ ┌──────────────────┼──────────────────┐ │ │
|
||||
│ │ ▼ ▼ ▼ │ │
|
||||
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
||||
│ │ │ Real Driver │ │ Mock Driver │ │ Mock Data │ │ │
|
||||
│ │ │ (Production)│ │ (Dev/Mock) │ │ (Static) │ │ │
|
||||
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 實現方案
|
||||
|
||||
### 方案 1: 編譯時開關 (推薦)
|
||||
|
||||
```c
|
||||
// hInclude.h
|
||||
#ifdef ENABLE_MOCK_MODE
|
||||
#include "mock_driver.h"
|
||||
#define DevIoControl mock_DevIoControl
|
||||
#define QuaryDTR mock_QuaryDTR
|
||||
#define GetCtlrEvent mock_GetCtlrEvent
|
||||
#endif
|
||||
```
|
||||
|
||||
### 方案 2: 執行時開關
|
||||
|
||||
```c
|
||||
// runtime_config.h
|
||||
extern BOOL g_bMockMode;
|
||||
|
||||
// 初始化時檢查
|
||||
if (g_bMockMode) {
|
||||
initMockDriver();
|
||||
} else {
|
||||
initRealDriver();
|
||||
}
|
||||
```
|
||||
|
||||
### 方案 3: 環境變數
|
||||
|
||||
```bash
|
||||
# 啟動時設定
|
||||
export DTR_GUI_MOCK_MODE=1
|
||||
./DtrGuiSrv01
|
||||
```
|
||||
|
||||
## 需要實現的 Mock 函數
|
||||
|
||||
### 1. mock_driver.h
|
||||
|
||||
```c
|
||||
#ifndef MOCK_DRIVER_H
|
||||
#define MOCK_DRIVER_H
|
||||
|
||||
#include "hInclude.h"
|
||||
|
||||
// Mock 初始化
|
||||
void mock_init(void);
|
||||
void mock_cleanup(void);
|
||||
|
||||
// Mock 控制器數據
|
||||
typedef struct {
|
||||
UCHAR bStatus;
|
||||
UCHAR bCtlrSN[16];
|
||||
UCHAR bModelName[32];
|
||||
UCHAR bPage0Data[1024]; // Controller Info Page
|
||||
UCHAR bPage1Data[1024]; // RAID Snapshot
|
||||
// ...
|
||||
} MOCK_CTRL_INFO;
|
||||
|
||||
// Mock DevIoControl
|
||||
UCHAR mock_DevIoControl(
|
||||
UCHAR bDevNo,
|
||||
ULONG lControlCode,
|
||||
ULONG lInSize,
|
||||
PCHAR pInBuf,
|
||||
ULONG lOutSize,
|
||||
PCHAR pOutBuf,
|
||||
USHORT *pusInBandErrCode
|
||||
);
|
||||
|
||||
// Mock QuaryDTR
|
||||
CHAR mock_QuaryDTR(
|
||||
UCHAR ucCtlrIndex,
|
||||
UCHAR *bPageBuf,
|
||||
UCHAR *pbHeatsink
|
||||
);
|
||||
|
||||
// Mock GetCtlrEvent
|
||||
UCHAR mock_GetCtlrEvent(
|
||||
UCHAR bCtlrNo,
|
||||
PEVENT_INFO pEvent_info
|
||||
);
|
||||
|
||||
// 設定模擬事件
|
||||
void mock_set_event(UCHAR *eventData, int length);
|
||||
void mock_clear_events(void);
|
||||
|
||||
// 控制器狀態控制
|
||||
void mock_set_controller_online(BOOL online);
|
||||
void mock_set_controller_count(int count);
|
||||
|
||||
#endif
|
||||
```
|
||||
|
||||
### 2. mock_driver.c
|
||||
|
||||
```c
|
||||
#include "mock_driver.h"
|
||||
|
||||
// 單例 Mock 控制器資訊
|
||||
static MOCK_CTRL_INFO g_mockCtrl[MAX_DTR_IN_HOST];
|
||||
static BOOL g_bMockInitialized = FALSE;
|
||||
|
||||
// 模擬的事件佇列
|
||||
static UCHAR g_mockEventQueue[100][512];
|
||||
static int g_mockEventCount = 0;
|
||||
|
||||
void mock_init(void) {
|
||||
if (g_bMockInitialized) return;
|
||||
|
||||
// 初始化 Mock 控制器
|
||||
memset(g_mockCtrl, 0, sizeof(g_mockCtrl));
|
||||
|
||||
// 預設控制器 1 台,狀態上線
|
||||
g_mockCtrl[0].bStatus = CTLR_STATUS_ALIVE;
|
||||
memcpy(g_mockCtrl[0].bCtlrSN, "MOCK00000001", 16);
|
||||
memcpy(g_mockCtrl[0].bModelName, "Accusys Mock RAID", 32);
|
||||
|
||||
// 填充 Page 0 資料 (Controller Info)
|
||||
fill_mock_page0(g_mockCtrl[0].bPage0Data);
|
||||
|
||||
g_bMockInitialized = TRUE;
|
||||
|
||||
printf("[MOCK] Driver initialized\n");
|
||||
}
|
||||
|
||||
UCHAR mock_DevIoControl(...) {
|
||||
if (!g_bMockInitialized) mock_init();
|
||||
|
||||
// 根據 Control Code 處理
|
||||
switch (lControlCode) {
|
||||
case IOCTL_DTR_GET_CONTROLLER_INFO:
|
||||
// 回傳控制器資訊
|
||||
break;
|
||||
|
||||
case IOCTL_DTR_GET_INBAND_EVENT:
|
||||
// 回傳模擬事件
|
||||
break;
|
||||
|
||||
case IOCTL_DTR_SET_INBAND_COMMAND:
|
||||
// 處理命令,回傳成功
|
||||
break;
|
||||
|
||||
default:
|
||||
// 預設回傳成功
|
||||
*pusInBandErrCode = 0;
|
||||
return SYSOK;
|
||||
}
|
||||
|
||||
return SYSOK;
|
||||
}
|
||||
|
||||
CHAR mock_QuaryDTR(UCHAR ucCtlrIndex, UCHAR *bPageBuf, UCHAR *pbHeatsink) {
|
||||
if (!g_bMockInitialized) mock_init();
|
||||
|
||||
if (ucCtlrIndex >= MAX_DTR_IN_HOST) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 複製 Mock 資料
|
||||
memcpy(bPageBuf, g_mockCtrl[ucCtlrIndex].bPage0Data, 1024);
|
||||
*pbHeatsink = 0x00;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
UCHAR mock_GetCtlrEvent(UCHAR bCtlrNo, PEVENT_INFO pEvent_info) {
|
||||
if (!g_bMockInitialized) mock_init();
|
||||
|
||||
// 從佇列取出事件
|
||||
if (g_mockEventCount > 0) {
|
||||
memcpy(pEvent_info->bEventBuf, g_mockEventQueue[0], 512);
|
||||
pEvent_info->bStatus = TRUE;
|
||||
|
||||
// 移除已取出的事件
|
||||
g_mockEventCount--;
|
||||
// ... 佇列搬移
|
||||
|
||||
return SYSOK;
|
||||
}
|
||||
|
||||
pEvent_info->bStatus = FALSE;
|
||||
return SYSFAIL_EVENT_EMPTY;
|
||||
}
|
||||
```
|
||||
|
||||
## 模擬的 Page 資料
|
||||
|
||||
### Page 0: Controller Info
|
||||
|
||||
```c
|
||||
void fill_mock_page0(UCHAR *buf) {
|
||||
// Offset 0-31: Serial Number
|
||||
memcpy(buf + 0, "MOCK00000001", 16);
|
||||
|
||||
// Offset 32-63: Model Name
|
||||
memcpy(buf + 32, "Accusys Mock RAID", 20);
|
||||
|
||||
// Offset 64-95: Firmware Version
|
||||
memcpy(buf + 64, "1.0.0.0", 8);
|
||||
|
||||
// 其他欄位...
|
||||
// 需要完整對應 Java GUI 解析的格式
|
||||
}
|
||||
```
|
||||
|
||||
### 需要的 Page
|
||||
|
||||
| Page | 大小 | 用途 |
|
||||
|------|------|------|
|
||||
| 0x00 | 1024 | Controller Info |
|
||||
| 0x01 | 1024 | RAID Snapshot |
|
||||
| 0x07 | 512 | LUN Map Table |
|
||||
| 0x0A-0x0E | 1024 | RAID Info |
|
||||
| 0x10-0x11 | 512 | Disk/Slice Names |
|
||||
| 0x14 | 512 | Array Info |
|
||||
| 0x1A | 512 | Enclosure Info |
|
||||
| 0x1B | 512 | Disk List |
|
||||
|
||||
## 模擬事件
|
||||
|
||||
### 預設模擬事件
|
||||
|
||||
```c
|
||||
// 初始化時註冊一些模擬事件
|
||||
void mock_register_default_events(void) {
|
||||
// 磁碟上線事件
|
||||
UCHAR diskOnlineEvent[] = {
|
||||
0x01, 0x00, 0x00, 0x00, // Event Type
|
||||
0x00, 0x00, 0x00, 0x00, // Timestamp
|
||||
0x01, 0x02, 0x00, 0x00, // Slot 1, Online
|
||||
// ...
|
||||
};
|
||||
mock_set_event(diskOnlineEvent, 32);
|
||||
|
||||
// RAID 重建完成事件
|
||||
UCHAR rebuildDoneEvent[] = {
|
||||
0x02, 0x00, 0x00, 0x00, // Event Type
|
||||
// ...
|
||||
};
|
||||
mock_set_event(rebuildDoneEvent, 32);
|
||||
}
|
||||
```
|
||||
|
||||
## 配置檔案
|
||||
|
||||
### mock_config.json
|
||||
|
||||
```json
|
||||
{
|
||||
"mock_mode": true,
|
||||
"controller_count": 2,
|
||||
"controllers": [
|
||||
{
|
||||
"serial_number": "MOCK00000001",
|
||||
"model_name": "Accusys GEN2",
|
||||
"firmware_version": "3.8.0",
|
||||
"status": "online",
|
||||
"raid_arrays": [
|
||||
{
|
||||
"id": 0,
|
||||
"level": "RAID5",
|
||||
"disks": [0, 1, 2, 3],
|
||||
"status": "Normal",
|
||||
"capacity": 10000000
|
||||
}
|
||||
],
|
||||
"disks": [
|
||||
{"slot": 0, "status": "Online", "capacity": 4000000},
|
||||
{"slot": 1, "status": "Online", "capacity": 4000000},
|
||||
{"slot": 2, "status": "Online", "capacity": 4000000},
|
||||
{"slot": 3, "status": "Online", "capacity": 4000000},
|
||||
{"slot": 4, "status": "Online", "capacity": 4000000, "spare": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"serial_number": "MOCK00000002",
|
||||
"model_name": "Accusys GEN2",
|
||||
"status": "online"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
{
|
||||
"type": "DiskOnline",
|
||||
"slot": 2,
|
||||
"timestamp": 1234567890
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 編譯選項
|
||||
|
||||
### Makefile
|
||||
|
||||
```makefile
|
||||
# 一般編譯
|
||||
build:
|
||||
gcc -o DtrGuiSrv01 *.c -lpthread -lm
|
||||
|
||||
# Mock 模式編譯
|
||||
build-mock:
|
||||
gcc -o DtrGuiSrv01_mock *.c -DENABLE_MOCK_MODE -lpthread -lm
|
||||
|
||||
# 兩者區別
|
||||
ifdef ENABLE_MOCK_MODE
|
||||
CFLAGS += -DENABLE_MOCK_MODE
|
||||
SRCS += mock_driver.c
|
||||
endif
|
||||
```
|
||||
|
||||
### CMake
|
||||
|
||||
```cmake
|
||||
option(ENABLE_MOCK "Enable mock mode for testing" OFF)
|
||||
|
||||
if(ENABLE_MOCK)
|
||||
add_definitions(-DENABLE_MOCK_MODE)
|
||||
set(MOCK_SOURCES mock_driver.c)
|
||||
endif()
|
||||
|
||||
add_executable(DtrGuiSrv01 ${SOURCES} ${MOCK_SOURCES})
|
||||
```
|
||||
|
||||
## 執行模式
|
||||
|
||||
### 1. 環境變數
|
||||
|
||||
```bash
|
||||
# 啟用 Mock 模式
|
||||
export DTR_GUI_MOCK_MODE=1
|
||||
./DtrGuiSrv01
|
||||
|
||||
# 指定 Mock 設定檔
|
||||
export DTR_GUI_MOCK_CONFIG=./mock_config.json
|
||||
./DtrGuiSrv01
|
||||
```
|
||||
|
||||
### 2. 命令列參數
|
||||
|
||||
```bash
|
||||
./DtrGuiSrv01 --mock
|
||||
./DtrGuiSrv01 -m
|
||||
./DtrGuiSrv01 --mock-config ./mock_config.json
|
||||
```
|
||||
|
||||
### 3. 設定檔
|
||||
|
||||
```ini
|
||||
# dtr_guiconf.ini
|
||||
[Server]
|
||||
Port=8922
|
||||
MockMode=1
|
||||
MockConfig=./mock_config.json
|
||||
```
|
||||
|
||||
## 測試案例
|
||||
|
||||
### 1. 基本連線測試
|
||||
|
||||
```python
|
||||
import socket
|
||||
|
||||
def test_connection():
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect(('localhost', 8922))
|
||||
|
||||
# 發送 GET_PAGE 請求
|
||||
request = build_get_page_request(page_code=0x00)
|
||||
s.send(request)
|
||||
|
||||
# 驗證回應
|
||||
response = s.recv(4096)
|
||||
assert response[0] == 0x50 # Success
|
||||
|
||||
s.close()
|
||||
```
|
||||
|
||||
### 2. 控制器查詢測試
|
||||
|
||||
```python
|
||||
def test_query_controller():
|
||||
# 查詢控制器資訊
|
||||
response = send_inband_command(opcode=0x01, page=0x00)
|
||||
|
||||
# 驗證資料格式
|
||||
assert response['serial_number'] == 'MOCK00000001'
|
||||
assert response['model_name'] == 'Accusys Mock RAID'
|
||||
```
|
||||
|
||||
### 3. 事件測試
|
||||
|
||||
```python
|
||||
def test_event_polling():
|
||||
# 設定模擬事件
|
||||
mock_set_event(disk_online_event)
|
||||
|
||||
# 取得事件
|
||||
response = send_event_request(opcode=0x02)
|
||||
|
||||
# 驗證事件
|
||||
assert response['event_type'] == 'DiskOnline'
|
||||
```
|
||||
|
||||
## 實作時間評估
|
||||
|
||||
| 工作項目 | 時間 |
|
||||
|----------|------|
|
||||
| 建立 mock_driver.h/c 框架 | 1 天 |
|
||||
| 實作基本 DevIoControl | 2 天 |
|
||||
| 實作 QuaryDTR / GetCtlrEvent | 1 天 |
|
||||
| 實作 Page 0 (Controller Info) | 1 天 |
|
||||
| 實作其他 Page 資料 | 2-3 天 |
|
||||
| 模擬事件系統 | 1 天 |
|
||||
| JSON 設定檔支援 | 1 天 |
|
||||
| 測試與除錯 | 2 天 |
|
||||
| **總計** | **9-10 天** |
|
||||
|
||||
## 風險與限制
|
||||
|
||||
### 已知限制
|
||||
|
||||
1. **不完整模擬**: 無法完全模擬所有 RAID 操作
|
||||
2. **狀態有限**: 只能模擬基本狀態
|
||||
3. **Driver 特定**: 某些低層級錯誤無法模擬
|
||||
|
||||
### 風險控制
|
||||
|
||||
1. **清晰標記**: Mock 模式下顯示明顯標識
|
||||
2. **日誌區分**: Mock 日誌使用不同前綴 `[MOCK]`
|
||||
3. **版本標記**: 回應中包含 Mock 標記
|
||||
|
||||
```c
|
||||
// 回應中加入 Mock 標記
|
||||
UCHAR mock_page0[] = {
|
||||
// ...
|
||||
'M', 'O', 'C', 'K', // 識別碼
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
## 結論
|
||||
|
||||
### 可行性: ✅ 高
|
||||
|
||||
**結論**: 完全可行,預估 9-10 天可完成。
|
||||
|
||||
### 實作策略
|
||||
|
||||
1. **先實現基本框架**: 快速建立可運行的 Mock 骨架
|
||||
2. **逐步完善**: 按需求添加更多 Page 支援
|
||||
3. **測試驅動**: 先寫測試案例,再實作對應 Mock
|
||||
|
||||
### 優點
|
||||
|
||||
- 開發期不需要實際 RAID 硬體
|
||||
- CI/CD 可自動化測試
|
||||
- 容易重現問題
|
||||
- 教學演示方便
|
||||
|
||||
### 產出
|
||||
|
||||
- `mock_driver.h` - Mock 驅動介面
|
||||
- `mock_driver.c` - Mock 驅動實作
|
||||
- `mock_config.json` - 模擬設定檔範例
|
||||
- 編譯腳本更新
|
||||
@@ -0,0 +1,361 @@
|
||||
# GS (DtrGuiSrv) Rust 重構分析
|
||||
|
||||
## 概述
|
||||
|
||||
分析是否可將 GS (DtrGuiSrv) C 語言程式完整用 Rust 重構為 `raidguard_x_gui_server`。
|
||||
|
||||
## 現有 GS 元件分析
|
||||
|
||||
### GS (C) 元件清單
|
||||
|
||||
| 檔案 | 功能 | 複雜度 | 依賴 |
|
||||
|------|------|--------|------|
|
||||
| cMain.c | 主程式入口、初始化 | 中 | 所有模組 |
|
||||
| cNetWork.c | TCP 網路通訊 (Port 8922) | 高 | socket API |
|
||||
| cInBand.c | In-Band API 處理 | 高 | RAID Driver |
|
||||
| cPollingEvent.c | 事件輪詢執行緒 | 中 | 佇列、檔案 |
|
||||
| cMail.c | Email 發送 (SMTP) | 中 | SMTP library |
|
||||
| cShareMem.c | 共享記憶體 | 中 | POSIX SHM |
|
||||
| cSrvConfig.c | 伺服器設定 | 低 | 檔案 I/O |
|
||||
| cDTRIoCtl.c | RAID I/O Control | 高 | Driver API |
|
||||
|
||||
### 每個元件的關鍵功能
|
||||
|
||||
```
|
||||
cMain.c
|
||||
├── InitGUIServer() - 初始化
|
||||
├── InitSMTP() - SMTP 初始化
|
||||
├── InitRAIDCard() - RAID 卡初始化
|
||||
└── Main Loop - 網路監聽
|
||||
|
||||
cNetWork.c
|
||||
├── InitialSrvSocket() - 建立 TCP Server (Port 8922)
|
||||
├── InitialClientSocket() - accept()
|
||||
├── GUITrans() - TCP 傳輸 (Phase1 + Phase2)
|
||||
└── ServerGUITrans() - 伺服器端傳輸
|
||||
|
||||
cInBand.c
|
||||
├── HandleInBandReq() - 處理 In-Band 請求
|
||||
├── HandleSrvConfigReq() - 處理伺服器設定
|
||||
└── [RAID Driver I/O] - 與硬體通訊
|
||||
|
||||
cPollingEvent.c
|
||||
├── PollingCtlrEvent() - 執行緒:輪詢事件
|
||||
├── SaveInBandEvent() - 儲存到記憶體佇列
|
||||
├── RecEventToFile() - 寫入事件檔案
|
||||
└── HandleInBandEventReq() - 處理 GC 請求
|
||||
|
||||
cMail.c
|
||||
├── InitMailEntryFreeQ() - 初始化郵件佇列
|
||||
├── IssueMailOut() - 發送郵件
|
||||
└── LoadMailAddrFromFile() - 載入郵件地址
|
||||
|
||||
cShareMem.c
|
||||
├── InitialSHMForAllCtlr() - 初始化共享記憶體
|
||||
└── 與 RAID Driver 共享資料
|
||||
|
||||
cDTRIoCtl.c
|
||||
├── QuaryDTR() - 查詢 RAID 資訊
|
||||
├── DevIoControl() - I/O Control
|
||||
└── 硬體驅動程式介面
|
||||
```
|
||||
|
||||
## 重構可行性分析
|
||||
|
||||
### 可以重構的部分 (✅)
|
||||
|
||||
| 元件 | Rust 方案 | 難度 |
|
||||
|------|-----------|------|
|
||||
| TCP 網路 | tokio, async-std | 中 |
|
||||
| 協議解析 | nom, manual parsing | 低 |
|
||||
| 事件輪詢 | tokio::spawn, interval | 低 |
|
||||
| 設定管理 | serde_json, toml | 低 |
|
||||
| 日誌系統 | tracing, log | 低 |
|
||||
| Email 發送 | lettre | 中 |
|
||||
| 錯誤處理 | thiserror, anyhow | 低 |
|
||||
|
||||
### 難以重構的部分 (⚠️)
|
||||
|
||||
| 元件 | 問題 | 建議 |
|
||||
|------|------|------|
|
||||
| cShareMem.c | POSIX 共享記憶體依賴 | 使用 rust-posix-shmem 或 FFI |
|
||||
| cDTRIoCtl.c | RAID Driver I/O | 需要 FFI 綁定或重寫 Driver |
|
||||
| 硬體驅動 | 低層級硬體存取 | 保持 C 或用 FFI |
|
||||
|
||||
### 需要 FFI 的部分
|
||||
|
||||
```rust
|
||||
// 使用 FFI 呼叫現有 C 函數
|
||||
#[repr(C)]
|
||||
pub struct CtlrInfo {
|
||||
pub bStatus: u8,
|
||||
pub bCtlrSN: [c_char; 16],
|
||||
// ...
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn QuaryDTR(bDevNo: u8, bPageBuf: *mut u8, pbHeatsink: *mut u8) -> c_short;
|
||||
fn DevIoControl(...) -> c_uchar;
|
||||
fn GetCtlSemP(DeviceNum: c_short) -> c_short;
|
||||
fn GetCtlSemV(DeviceNum: c_short) -> c_short;
|
||||
}
|
||||
```
|
||||
|
||||
## 現有 Rust 專案結構
|
||||
|
||||
```
|
||||
raidguard_x_gui_server/
|
||||
├── Cargo.toml # tokio, serde, tracing, lettre
|
||||
├── src/
|
||||
│ ├── main.rs # 入口
|
||||
│ ├── lib.rs # library
|
||||
│ ├── server.rs # TCP Server
|
||||
│ ├── protocol/ # 協議解析
|
||||
│ ├── handlers/ # 請求處理
|
||||
│ ├── mock/ # 模擬數據
|
||||
│ └── backend/ # 後端
|
||||
```
|
||||
|
||||
## 重構架構建議
|
||||
|
||||
### 模組化設計
|
||||
|
||||
```
|
||||
raidguard_x_gui_server/
|
||||
├── src/
|
||||
│ ├── main.rs
|
||||
│ ├── lib.rs
|
||||
│ │
|
||||
│ ├── network/ # 網路通訊
|
||||
│ │ ├── mod.rs
|
||||
│ │ ├── server.rs # TCP Server (Port 8922)
|
||||
│ │ ├── transport.rs # Phase1/Phase2 傳輸
|
||||
│ │ └── client.rs # Client 連線管理
|
||||
│ │
|
||||
│ ├── protocol/ # 協議
|
||||
│ │ ├── mod.rs
|
||||
│ │ ├── request.rs # 請求解析
|
||||
│ │ ├── response.rs # 回應封裝
|
||||
│ │ └── error.rs # 協議錯誤
|
||||
│ │
|
||||
│ ├── inband/ # In-Band API
|
||||
│ │ ├── mod.rs
|
||||
│ │ ├── handler.rs # 請求處理
|
||||
│ │ ├── page.rs # Page 資料
|
||||
│ │ └── password.rs # 密碼處理
|
||||
│ │
|
||||
│ ├── event/ # 事件處理
|
||||
│ │ ├── mod.rs
|
||||
│ │ ├── queue.rs # 事件佇列
|
||||
│ │ ├── polling.rs # 輪詢執行緒
|
||||
│ │ └── storage.rs # 事件儲存
|
||||
│ │
|
||||
│ ├── email/ # Email 通知
|
||||
│ │ ├── mod.rs
|
||||
│ │ ├── smtp.rs # SMTP 發送
|
||||
│ │ └── template.rs # 郵件範本
|
||||
│ │
|
||||
│ ├── config/ # 設定管理
|
||||
│ │ ├── mod.rs
|
||||
│ │ ├── server.rs # 伺服器設定
|
||||
│ │ └── storage.rs # 設定儲存
|
||||
│ │
|
||||
│ ├── driver/ # RAID Driver (FFI)
|
||||
│ │ ├── mod.rs
|
||||
│ │ ├── ffi.rs # FFI 綁定
|
||||
│ │ └── types.rs # C 類型包裝
|
||||
│ │
|
||||
│ └── logging/ # 日誌
|
||||
│ └── mod.rs
|
||||
```
|
||||
|
||||
### 技術選型
|
||||
|
||||
| 功能 | Rust 選擇 | 說明 |
|
||||
|------|----------|------|
|
||||
| 非同步 runtime | tokio | 完整 async/await 支援 |
|
||||
| TCP 網路 | tokio::net | 內建 TCP 支援 |
|
||||
| 協議解析 | nom / 手動 | 簡單協議,手動即可 |
|
||||
| 序列化 | serde | JSON 支援 |
|
||||
| Email | lettre | SMTP client |
|
||||
| 日誌 | tracing | 結構化日誌 |
|
||||
| 錯誤處理 | thiserror | 錯誤鏈 |
|
||||
| 共享記憶體 | libc::sys::shm | FFI |
|
||||
| 執行緒安全 | Arc<Mutex<>> | 內建 |
|
||||
|
||||
## 重構階段規劃
|
||||
|
||||
### Phase 1: 基礎設施 (1-2 週)
|
||||
|
||||
- [ ] 專案初始化 (現有)
|
||||
- [ ] TCP Server (Port 8922)
|
||||
- [ ] 協議解析 (Phase1/Phase2)
|
||||
- [ ] 基本請求/回應處理
|
||||
- [ ] 日誌系統
|
||||
|
||||
### Phase 2: In-Band API (2-3 週)
|
||||
|
||||
- [ ] GET_PAGE 處理 (0x01)
|
||||
- [ ] SEND_PASSWORD 處理 (0x1D)
|
||||
- [ ] 其他 OpCode 處理
|
||||
- [ ] 與 RAID Driver FFI
|
||||
|
||||
### Phase 3: 事件系統 (1-2 週)
|
||||
|
||||
- [ ] 事件輪詢執行緒
|
||||
- [ ] 事件佇列
|
||||
- [ ] 事件儲存 (檔案)
|
||||
- [ ] GC 事件查詢
|
||||
|
||||
### Phase 4: 通知系統 (1 週)
|
||||
|
||||
- [ ] Email 發送 (SMTP)
|
||||
- [ ] SNMP Trap (可選)
|
||||
- [ ] 設定管理
|
||||
|
||||
### Phase 5: 整合測試 (1-2 週)
|
||||
|
||||
- [ ] 單元測試
|
||||
- [ ] 整合測試
|
||||
- [ ] 效能優化
|
||||
- [ ] 文檔
|
||||
|
||||
## 挑戰與解決方案
|
||||
|
||||
### 挑戰 1: RAID Driver I/O
|
||||
|
||||
**問題**: cDTRIoctl.c 需要與硬體驅動程式直接通訊
|
||||
|
||||
**解決方案**:
|
||||
```rust
|
||||
// 方案 A: FFI 呼叫現有 C 函數
|
||||
#[link(name = "DtrIoCtl")]
|
||||
extern "C" {
|
||||
fn DevIoControl(...) -> c_uchar;
|
||||
}
|
||||
|
||||
// 方案 B: 將 Driver 程式碼移植到 Rust
|
||||
// (需要大量時間)
|
||||
|
||||
// 方案 C: 保持 GS 為 C,只重構 GUI 部分
|
||||
// (推薦: 混合架構)
|
||||
```
|
||||
|
||||
### 挑戰 2: 共享記憶體
|
||||
|
||||
**問題**: cShareMem.c 使用 POSIX 共享記憶體
|
||||
|
||||
**解決方案**:
|
||||
```rust
|
||||
// 使用 nix crate
|
||||
use nix::sys::shm::{shmat, shmget, ShmFlags};
|
||||
|
||||
// 或使用 libc
|
||||
use libc::{shmget, shmat, shmctl, IPC_RMID};
|
||||
```
|
||||
|
||||
### 挑戰 3: 執行緒同步
|
||||
|
||||
**問題**: C 的 Semaphore 需要轉換
|
||||
|
||||
**解決方案**:
|
||||
```rust
|
||||
// Rust 建議使用
|
||||
use std::sync::{Mutex, Semaphore, Arc};
|
||||
|
||||
// 或使用 tokio::sync
|
||||
use tokio::sync::{Mutex, Semaphore};
|
||||
```
|
||||
|
||||
### 挑戰 4: 相容性
|
||||
|
||||
**問題**: 需要完全相容現有 GC (Java) 協議
|
||||
|
||||
**解決方案**:
|
||||
```rust
|
||||
// 確保位元組級別完全相同
|
||||
#[repr(C)]
|
||||
struct RequestHeader {
|
||||
pub req_type: u8,
|
||||
pub phase_code: u8,
|
||||
pub length: u16, // Little-endian
|
||||
}
|
||||
|
||||
// 測試覆蓋所有現有功能
|
||||
```
|
||||
|
||||
## 推薦策略
|
||||
|
||||
### 策略 A: 完全重構 (全部 Rust)
|
||||
|
||||
**優點**:
|
||||
- 單一語言,維護簡單
|
||||
- 記憶體安全
|
||||
- 現代化生態
|
||||
|
||||
**缺點**:
|
||||
- 需要 FFI 或重寫 Driver
|
||||
- 開發時間長
|
||||
|
||||
**時間預估**: 3-4 個月
|
||||
|
||||
### 策略 B: 混合架構 (推薦)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 混合架構 │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ GC (Java) │ │ Rust Server │ │ C Driver │ │
|
||||
│ │ Port 8922 │◄──►│ │◄──►│ (現有) │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ └─────────────┘ └──────┬──────┘ └─────────────┘ │
|
||||
│ │ │
|
||||
│ 共享記憶體/FDI │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**優點**:
|
||||
- 保留穩定的 C Driver
|
||||
- Rust 處理網路/業務邏輯
|
||||
- 逐步遷移
|
||||
|
||||
**缺點**:
|
||||
- FFI 複雜度
|
||||
|
||||
**時間預估**: 2-3 個月
|
||||
|
||||
### 策略 C: 只重構 GUI Client
|
||||
|
||||
維持 GS 為 C 程式,只重構 GC (Java) 為 Rust GUI Client。
|
||||
|
||||
## 結論
|
||||
|
||||
**可以完整重構,但建議採用混合策略:**
|
||||
|
||||
1. **網路/協議/事件/Email** → Rust (可完全重構)
|
||||
2. **RAID Driver/共享記憶體** → 保持 C 或 FFI
|
||||
3. **優先順序**: 先重構 GC (Java→Rust) → 再考慮 GS
|
||||
|
||||
**主要原因**:
|
||||
- Driver 層變動風險高
|
||||
- GS 相對穩定,需求變化少
|
||||
- GC (GUI) 更有現代化價值
|
||||
|
||||
## 現有程式碼利用率
|
||||
|
||||
```
|
||||
raidguard_x_gui_server (現有)
|
||||
├── ✅ network/server.rs → 可基於 cNetWork.c
|
||||
├── ✅ protocol/ → 可基於 GS 協議
|
||||
├── ⚠️ handlers/ → 需要實作 In-Band API
|
||||
└── ❌ backend/ → 需要連接 RAID Driver
|
||||
```
|
||||
|
||||
## 下一步建議
|
||||
|
||||
1. **短期**: 完成現有 Rust Server 與 GS 的替換
|
||||
2. **中期**: FFI 整合現有 C Driver
|
||||
3. **長期**: 逐步將 Driver 移植到 Rust (如有需要)
|
||||
@@ -0,0 +1,268 @@
|
||||
# RAIDGuard X - GUI Client/Server Architecture
|
||||
|
||||
A modern Rust-based GUI application for managing RAID storage systems.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ raidguard_x_gui_client (Slint GUI) │
|
||||
│ - Cross-platform UI (macOS, Windows, Linux) │
|
||||
│ - TCP connection to server │
|
||||
│ - Real-time data display │
|
||||
└─────────────────────────┬───────────────────────────────────────────┘
|
||||
│ TCP JSON Protocol (:8923)
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ raidguard_x_gui_server (Mock/Real Backend) │
|
||||
│ - Provides RAID controller data │
|
||||
│ - Currently serves mock data │
|
||||
│ - Future: In-Band API to real RAID systems │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
raidguard_x_gui_server/ # Data server
|
||||
├── Cargo.toml
|
||||
├── src/
|
||||
│ ├── main.rs # Entry point
|
||||
│ ├── lib.rs
|
||||
│ ├── server.rs # TCP server
|
||||
│ ├── protocol/ # JSON message protocol
|
||||
│ ├── handlers/ # Request handlers
|
||||
│ └── mock/ # Mock data provider
|
||||
|
||||
raidguard_x_gui_client/ # GUI client
|
||||
├── Cargo.toml
|
||||
├── build.rs # Slint build script
|
||||
├── src/
|
||||
│ ├── main.rs # Entry point + Slint integration
|
||||
│ ├── lib.rs
|
||||
│ ├── app.rs # App state management
|
||||
│ ├── models/ # Data models
|
||||
│ └── protocol/ # Client protocol
|
||||
└── ui/
|
||||
└── main_window.slint # Slint UI definition
|
||||
```
|
||||
|
||||
## Build Requirements
|
||||
|
||||
- Rust 1.70+
|
||||
- For GUI client: `xcode-select` (macOS) or equivalent
|
||||
|
||||
## Build Instructions
|
||||
|
||||
### Server
|
||||
|
||||
```bash
|
||||
cd raidguard_x_gui_server
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
Run:
|
||||
```bash
|
||||
# Default port 8923
|
||||
cargo run --release
|
||||
|
||||
# Custom port
|
||||
SERVER_ADDR=127.0.0.1:8923 cargo run --release
|
||||
```
|
||||
|
||||
### Client
|
||||
|
||||
```bash
|
||||
cd raidguard_x_gui_client
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
Run:
|
||||
```bash
|
||||
cargo run --release
|
||||
```
|
||||
|
||||
## Protocol
|
||||
|
||||
### Request Format
|
||||
```json
|
||||
{
|
||||
"id": "uuid-v4",
|
||||
"type": "request",
|
||||
"action": "get_controllers",
|
||||
"params": {}
|
||||
}
|
||||
```
|
||||
|
||||
### Response Format
|
||||
```json
|
||||
{
|
||||
"id": "uuid-v4",
|
||||
"type": "response",
|
||||
"status": "success",
|
||||
"data": { ... },
|
||||
"error": null
|
||||
}
|
||||
```
|
||||
|
||||
### Available Actions
|
||||
|
||||
| Action | Description |
|
||||
|--------|-------------|
|
||||
| `get_controllers` | Get all controllers |
|
||||
| `get_raids` | Get RAID arrays |
|
||||
| `get_disks` | Get physical disks |
|
||||
| `get_events` | Get event log |
|
||||
| `ping` | Health check |
|
||||
|
||||
## Development
|
||||
|
||||
### Testing the Server
|
||||
|
||||
```bash
|
||||
# Terminal 1: Start server
|
||||
cd raidguard_x_gui_server
|
||||
cargo run --release
|
||||
|
||||
# Terminal 2: Connect with netcat
|
||||
nc localhost 8923
|
||||
|
||||
# Send request:
|
||||
{"id":"test","type":"request","action":"get_controllers","params":{}}
|
||||
|
||||
# Response:
|
||||
{"id":"test","type":"response","status":"success","data":{"controllers":[...]}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Menu Bar & Toolbar Functionality
|
||||
|
||||
### Menu Bar Structure
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ [File] [Controller] [Language] [Help] │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
```
|
||||
|
||||
#### File Menu
|
||||
| Item | Function |
|
||||
|------|----------|
|
||||
| Exit | Close application |
|
||||
| Load Controller List | Load saved controller list |
|
||||
| **Language** (submenu) | English / Japanese |
|
||||
|
||||
#### Controller Menu
|
||||
| Item | Function |
|
||||
|------|----------|
|
||||
| Manual Add Controller | Manually add a controller |
|
||||
| Update System Code | Firmware update |
|
||||
| Dump Controller Log | Export controller log |
|
||||
| Shutdown | Shutdown controller |
|
||||
| **Update** (submenu) | |
|
||||
| - Update Expander Code | Update expander firmware |
|
||||
| - Update JBOD Code | Update JBOD firmware |
|
||||
| - Update Boot Code | Update boot firmware |
|
||||
| - Update BIOS/EFI | Update BIOS/EFI |
|
||||
| Disk RW Test | Run disk read/write test |
|
||||
| Polling On/Off | Toggle polling |
|
||||
|
||||
#### Help Menu
|
||||
| Item | Function |
|
||||
|------|----------|
|
||||
| Help Center | Open local help |
|
||||
| Play Menu | Debug/testing menu |
|
||||
| About | Show About dialog |
|
||||
|
||||
### Toolbar Buttons
|
||||
|
||||
| Button | Function |
|
||||
|--------|----------|
|
||||
| **+** (Add) | Add Controller |
|
||||
| **-** (Remove) | Remove Controller |
|
||||
| **Create Array** | Create new RAID array |
|
||||
| **Delete Array** | Delete RAID array |
|
||||
| **Mirror** | Mirror configuration |
|
||||
| **Email** | Email notification settings |
|
||||
| **Settings** | Application preferences |
|
||||
| **Advanced** | Advanced operations menu |
|
||||
|
||||
### Advanced Menu (frmAdvanced)
|
||||
|
||||
| Category | Functions |
|
||||
|----------|-----------|
|
||||
| **LUN Mask** | LUN masking configuration |
|
||||
| **Slicing** | Storage slicing |
|
||||
| **Snapshot** | Snapshot management |
|
||||
| **Migrate** | Migration operations |
|
||||
| **Expansion** | Storage expansion |
|
||||
| **Maintenance** | System maintenance |
|
||||
| **Email Settings** | Email configuration |
|
||||
|
||||
---
|
||||
|
||||
## Java Source Code Architecture (Reference for Migration)
|
||||
|
||||
### 1. System Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ GUI Client (Swing) │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
|
||||
│ │ frmMain │ │ frmSettings │ │ frmCreateArray │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────────┘ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
|
||||
│ │ jpController │ │ dlgServer │ │ dlgDiskInfo │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────────┘ │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Global Data Layer │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ PageDataDB.java (~7300 lines) │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Protocol Layer │
|
||||
│ ┌──────────────────┐ ┌──────────────────────────┐ │
|
||||
│ │ InBandAPI │ │ SNMP │ │
|
||||
│ │ Port 8922 │ │ Port 162/8922 │ │
|
||||
│ └──────────────────┘ └──────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2. In-Band API Protocol (Port 8922)
|
||||
|
||||
**OpCodes:**
|
||||
| OpCode | Function |
|
||||
|--------|----------|
|
||||
| 0x01 | GET_PAGE (read info pages) |
|
||||
| 0x1D | SEND_PASSWORD (authentication) |
|
||||
| 0xBC | Unlock |
|
||||
| 0x1B | Erase RAID |
|
||||
| 0xCC | Create RAID |
|
||||
| 0xCE | Set Global Config |
|
||||
| 0x26 | Commit Config |
|
||||
|
||||
**Page Codes:**
|
||||
- Page 0: Controller Info
|
||||
- Page 1: RAID Snapshot
|
||||
- Page 7: LUN Map Table
|
||||
- Page 10-14: RAID Info
|
||||
- Page 16-17: Disk/Slice Names
|
||||
- Page 20: Array Info
|
||||
- Page 26: Enclosure Info
|
||||
- Page 27: Disk List
|
||||
|
||||
### 3. Key Java Files
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `InBandAPI/SendCmd.java` | TCP communication core |
|
||||
| `InBandAPI/BasePage.java` | Abstract base for page data |
|
||||
| `PageDataDB.java` | Global database (~7300 lines) |
|
||||
| `frmMain.java` | Main window (~318KB) |
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
Proprietary - Accusys Inc.
|
||||
@@ -1,3 +0,0 @@
|
||||
fn main() {
|
||||
slint_build::compile("ui/main_window.slint").unwrap();
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
# Build script for RAIDGuard X
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
echo "=== RAIDGuard X Build Script ==="
|
||||
echo ""
|
||||
|
||||
# Check Rust
|
||||
if ! command -v rustc &> /dev/null; then
|
||||
echo "ERROR: Rust is not installed"
|
||||
echo "Install from: https://rustup.rs"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Rust version: $(rustc --version)"
|
||||
echo ""
|
||||
|
||||
# Build server
|
||||
echo "=== Building Server ==="
|
||||
cd "$SCRIPT_DIR/raidguard_x_gui_server"
|
||||
cargo build --release
|
||||
echo "Server built successfully!"
|
||||
|
||||
# Build client
|
||||
echo ""
|
||||
echo "=== Building Client ==="
|
||||
cd "$SCRIPT_DIR/raidguard_x_gui_client"
|
||||
cargo build --release
|
||||
echo "Client built successfully!"
|
||||
|
||||
echo ""
|
||||
echo "=== Build Complete ==="
|
||||
echo ""
|
||||
echo "Executables:"
|
||||
echo " Server: raidguard_x_gui_server/target/release/raidguard-server"
|
||||
echo " Client: raidguard_x_gui_client/target/release/raidguard-client"
|
||||
echo ""
|
||||
echo "Run:"
|
||||
echo " Terminal 1: ./raidguard_x_gui_server/target/release/raidguard-server"
|
||||
echo " Terminal 2: ./raidguard_x_gui_client/target/release/raidguard-client"
|
||||
|
Before Width: | Height: | Size: 925 B |
|
Before Width: | Height: | Size: 827 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,4 @@
|
||||
*.o
|
||||
*.dSYM
|
||||
test_mock_driver
|
||||
test_all_pages
|
||||
@@ -0,0 +1,38 @@
|
||||
# Makefile for Mock Driver
|
||||
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -Wextra -g -O0
|
||||
SRCS = test_mock_driver.c mock_driver.c
|
||||
OBJS = $(SRCS:.c=.o)
|
||||
TARGET = test_mock_driver
|
||||
|
||||
# Test programs
|
||||
TEST_ALL = test_all_pages
|
||||
TEST_ALL_SRCS = test_all_pages.c mock_driver.c
|
||||
|
||||
all: $(TARGET) $(TEST_ALL)
|
||||
|
||||
$(TARGET): test_mock_driver.o mock_driver.o
|
||||
$(CC) $(CFLAGS) -o $@ test_mock_driver.o mock_driver.o
|
||||
|
||||
$(TEST_ALL): $(TEST_ALL_SRCS)
|
||||
$(CC) $(CFLAGS) -o $@ $(TEST_ALL_SRCS)
|
||||
|
||||
test_mock_driver.o: test_mock_driver.c mock_driver.h
|
||||
mock_driver.o: mock_driver.c mock_driver.h
|
||||
test_all_pages.o: test_all_pages.c mock_driver.h
|
||||
|
||||
clean:
|
||||
rm -f $(TARGET) $(TEST_ALL) *.o
|
||||
|
||||
run: $(TARGET)
|
||||
./$(TARGET)
|
||||
|
||||
run-all: $(TEST_ALL)
|
||||
./$(TEST_ALL)
|
||||
|
||||
# Enable verbose output
|
||||
debug: CFLAGS += -DVERBOSE=1
|
||||
debug: $(TARGET)
|
||||
|
||||
.PHONY: all clean run run-all debug
|
||||
@@ -0,0 +1,205 @@
|
||||
# RAIDGuard X Mock Driver (C)
|
||||
|
||||
Mock driver for RAIDGuard X GUI Server - provides simulated RAID controller for testing without hardware.
|
||||
|
||||
## 目錄結構
|
||||
|
||||
```
|
||||
raidguard_c_gui_server_mock/
|
||||
├── mock_driver.h # Header file
|
||||
├── mock_driver.c # Implementation
|
||||
├── test_mock_driver.c # Test program
|
||||
├── Makefile # Build file
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## 建構
|
||||
|
||||
```bash
|
||||
make
|
||||
```
|
||||
|
||||
## 執行測試
|
||||
|
||||
```bash
|
||||
./test_mock_driver # 基本測試
|
||||
./test_all_pages # 完整頁面測試
|
||||
```
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 1. 初始化
|
||||
|
||||
```c
|
||||
#include "mock_driver.h"
|
||||
|
||||
// 初始化 Mock Driver
|
||||
mock_init();
|
||||
|
||||
// 啟用詳細輸出
|
||||
mock_set_verbose(true);
|
||||
```
|
||||
|
||||
### 2. 設定控制器
|
||||
|
||||
```c
|
||||
// 設定控制器數量 (預設 1)
|
||||
mock_set_controller_count(2);
|
||||
|
||||
// 設定控制器序號
|
||||
mock_set_controller_serial(0, "MOCK00000001");
|
||||
mock_set_controller_serial(1, "MOCK00000002");
|
||||
|
||||
// 設定控制器型號
|
||||
mock_set_controller_model(0, "Accusys GEN2");
|
||||
```
|
||||
|
||||
### 3. 查詢控制器資訊
|
||||
|
||||
```c
|
||||
UCHAR pageBuf[1024];
|
||||
UCHAR heatsink;
|
||||
|
||||
// 查詢控制器資訊
|
||||
CHAR result = mock_QuaryDTR(0, pageBuf, &heatsink);
|
||||
|
||||
if (result == 1) {
|
||||
// 取得序號
|
||||
printf("SN: %.16s\n", pageBuf);
|
||||
|
||||
// 取得型號
|
||||
printf("Model: %.20s\n", pageBuf + 16);
|
||||
|
||||
// 取得韌體版本
|
||||
printf("FW: %.8s\n", pageBuf + 64);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 取得事件
|
||||
|
||||
```c
|
||||
EVENT_INFO evt;
|
||||
UCHAR result = mock_GetCtlrEvent(0, &evt);
|
||||
|
||||
if (result == SYSOK && evt.bStatus) {
|
||||
// 處理事件
|
||||
printf("Event Type: %d\n", evt.bEventBuf[0]);
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 模擬事件
|
||||
|
||||
```c
|
||||
// 模擬磁碟上線事件
|
||||
mock_generate_disk_event(5, 0x01);
|
||||
|
||||
// 模擬 RAID 重建事件
|
||||
mock_generate_raid_event(0, 0x02);
|
||||
|
||||
// 自訂事件
|
||||
UCHAR customEvent[] = {
|
||||
0x03, 0x00, 0x00, 0x00, // Event Type
|
||||
0x00, 0x00, 0x00, 0x00, // Timestamp
|
||||
0x00, 0x00, 0x00, 0x00, // Data
|
||||
0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
mock_push_event(customEvent, 16);
|
||||
```
|
||||
|
||||
### 6. 清理
|
||||
|
||||
```c
|
||||
mock_cleanup();
|
||||
```
|
||||
|
||||
## API 參考
|
||||
|
||||
### 初始化與清理
|
||||
|
||||
| 函數 | 說明 |
|
||||
|------|------|
|
||||
| `mock_init()` | 初始化 Mock Driver |
|
||||
| `mock_cleanup()` | 清理資源 |
|
||||
| `mock_set_verbose(bool)` | 設定詳細輸出 |
|
||||
|
||||
### 控制器設定
|
||||
|
||||
| 函數 | 說明 |
|
||||
|------|------|
|
||||
| `mock_set_controller_count(int)` | 設定控制器數量 |
|
||||
| `mock_set_controller_serial(int, const char*)` | 設定控制器序號 |
|
||||
| `mock_set_controller_model(int, const char*)` | 設定控制器型號 |
|
||||
| `mock_set_controller_status(int, UCHAR)` | 設定控制器狀態 |
|
||||
|
||||
### Driver 函數
|
||||
|
||||
| 函數 | 說明 |
|
||||
|------|------|
|
||||
| `mock_QuaryDTR(UCHAR, UCHAR*, UCHAR*)` | 查詢控制器資訊 |
|
||||
| `mock_DevIoControl(...)` | I/O Control (模擬) |
|
||||
| `mock_GetCtlrEvent(UCHAR, EVENT_INFO*)` | 取得事件 |
|
||||
|
||||
### 事件模擬
|
||||
|
||||
| 函數 | 說明 |
|
||||
|------|------|
|
||||
| `mock_push_event(UCHAR*, int)` | 推入自訂事件 |
|
||||
| `mock_clear_events()` | 清除所有事件 |
|
||||
| `mock_generate_disk_event(int, int)` | 產生磁碟事件 |
|
||||
| `mock_generate_raid_event(int, int)` | 產生 RAID 事件 |
|
||||
|
||||
## 模擬的 Page 資料
|
||||
|
||||
| Page | 大小 | 內容 |
|
||||
|------|------|------|
|
||||
| 0x00 | 1024 | Controller Info (序號, 型號, 韌體, 狀態) |
|
||||
| 0x01 | 1024 | RAID Snapshot (RAID 0-1 狀態) |
|
||||
| 0x07 | 512 | LUN Map Table |
|
||||
| 0x0A | 1024 | RAID Info 0-1 (狀態, 等級, 容量) |
|
||||
| 0x0B | 1024 | RAID Info 2-3 |
|
||||
| 0x0E | 1024 | Event Log (8 筆事件, 含時間戳記) |
|
||||
| 0x14 | 512 | Enclosure Info (風扇, 電源, 溫度) |
|
||||
| 0x1B | 512 | Disk List (8 個磁碟槽) |
|
||||
|
||||
## 整合到 GS
|
||||
|
||||
要將 Mock Driver 整合到 GS (DtrGuiSrv),請在編譯時使用 `-DENABLE_MOCK_MODE` :
|
||||
|
||||
```makefile
|
||||
ifdef ENABLE_MOCK_MODE
|
||||
CFLAGS += -DENABLE_MOCK_MODE
|
||||
SRCS += mock_driver.c
|
||||
|
||||
# 置換函數
|
||||
CFLAGS += -DQuaryDTR=mock_QuaryDTR
|
||||
CFLAGS += -DDevIoControl=mock_DevIoControl
|
||||
CFLAGS += -DGetCtlrEvent=mock_GetCtlrEvent
|
||||
endif
|
||||
```
|
||||
|
||||
或使用 FFI:
|
||||
|
||||
```c
|
||||
#ifdef ENABLE_MOCK_MODE
|
||||
#include "mock_driver.h"
|
||||
#define DevIoControl mock_DevIoControl
|
||||
#define QuaryDTR mock_QuaryDTR
|
||||
#define GetCtlrEvent mock_GetCtlrEvent
|
||||
#endif
|
||||
```
|
||||
|
||||
## 開發日誌
|
||||
|
||||
- 2026-03-29: 新增頁面
|
||||
- Page 14: Event Log (8 筆事件,含時間戳記與訊息)
|
||||
- Page 20: Enclosure Info (風扇、電源、溫度、電池)
|
||||
- test_all_pages 完整測試
|
||||
|
||||
- 2024-03-29: 初始版本
|
||||
- 基本控制器模擬
|
||||
- 事件佇列
|
||||
- Page 0-1, 7, 10, 27 資料
|
||||
|
||||
## License
|
||||
|
||||
Proprietary - Accusys Inc.
|
||||
@@ -0,0 +1,597 @@
|
||||
/*
|
||||
* mock_driver.c
|
||||
*
|
||||
* Mock Driver for RAIDGuard X GUI Server
|
||||
* Provides simulated RAID controller for testing without hardware
|
||||
*/
|
||||
|
||||
#include "mock_driver.h"
|
||||
#include <time.h>
|
||||
|
||||
// =============================================================================
|
||||
// Global Variables
|
||||
// =============================================================================
|
||||
|
||||
static MOCK_CTRL_INFO g_mockCtrl[MAX_DTR_IN_HOST];
|
||||
static UCHAR g_mockEventQueue[100][MAX_EDB_SZ];
|
||||
static int g_mockEventHead = 0;
|
||||
static int g_mockEventTail = 0;
|
||||
static int g_mockEventCount = 0;
|
||||
static bool g_bInitialized = false;
|
||||
|
||||
// Configuration
|
||||
static int g_controllerCount = 1;
|
||||
static bool g_verbose = false;
|
||||
|
||||
// =============================================================================
|
||||
// Initialization
|
||||
// =============================================================================
|
||||
|
||||
void mock_set_verbose(bool verbose) {
|
||||
g_verbose = verbose;
|
||||
}
|
||||
|
||||
bool mock_is_verbose(void) {
|
||||
return g_verbose;
|
||||
}
|
||||
|
||||
void mock_init(void) {
|
||||
if (g_bInitialized) return;
|
||||
|
||||
memset(g_mockCtrl, 0, sizeof(g_mockCtrl));
|
||||
memset(g_mockEventQueue, 0, sizeof(g_mockEventQueue));
|
||||
g_mockEventHead = 0;
|
||||
g_mockEventTail = 0;
|
||||
g_mockEventCount = 0;
|
||||
|
||||
// Initialize default controller
|
||||
g_controllerCount = 1;
|
||||
g_mockCtrl[0].bStatus = CTLR_STATUS_ALIVE;
|
||||
memcpy(g_mockCtrl[0].bCtlrSN, "MOCK00000001\0", 16);
|
||||
memcpy(g_mockCtrl[0].bModelName, "Accusys GEN2\0", 20);
|
||||
|
||||
// Fill mock page data
|
||||
mock_fill_page0(0);
|
||||
mock_fill_page1(0);
|
||||
mock_fill_page7(0);
|
||||
mock_fill_page10(0);
|
||||
mock_fill_page11(0);
|
||||
mock_fill_page14(0);
|
||||
mock_fill_page20(0);
|
||||
mock_fill_page27(0);
|
||||
|
||||
// Register default events
|
||||
mock_register_default_events();
|
||||
|
||||
g_bInitialized = true;
|
||||
|
||||
if (g_verbose) {
|
||||
printf("[MOCK] Driver initialized\n");
|
||||
}
|
||||
}
|
||||
|
||||
void mock_cleanup(void) {
|
||||
if (!g_bInitialized) return;
|
||||
|
||||
memset(g_mockCtrl, 0, sizeof(g_mockCtrl));
|
||||
memset(g_mockEventQueue, 0, sizeof(g_mockEventQueue));
|
||||
g_mockEventCount = 0;
|
||||
g_bInitialized = false;
|
||||
|
||||
if (g_verbose) {
|
||||
printf("[MOCK] Driver cleanup complete\n");
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Page Data Fillers
|
||||
// =============================================================================
|
||||
|
||||
void mock_fill_page0(int ctrlIndex) {
|
||||
MOCK_CTRL_INFO *ctrl = &g_mockCtrl[ctrlIndex];
|
||||
unsigned char *buf = ctrl->bPage0Data;
|
||||
|
||||
memset(buf, 0, 1024);
|
||||
|
||||
// Serial Number (Offset 0-15, 16 bytes)
|
||||
memcpy(buf + 0, ctrl->bCtlrSN, 16);
|
||||
|
||||
// Model Name (Offset 16-47, 32 bytes)
|
||||
memcpy(buf + 16, ctrl->bModelName, 20);
|
||||
|
||||
// Firmware Version (Offset 64-71, 8 bytes)
|
||||
memcpy(buf + 64, "3.8.0.0\0", 8);
|
||||
|
||||
// BIOS Version (Offset 72-79, 8 bytes)
|
||||
memcpy(buf + 72, "1.2.3.4\0", 8);
|
||||
|
||||
// Status (Offset 96)
|
||||
buf[96] = 0x01; // Online
|
||||
|
||||
// Number of RAID Ports (Offset 104)
|
||||
buf[104] = 4;
|
||||
|
||||
// Number of Disks (Offset 105)
|
||||
buf[105] = 8;
|
||||
|
||||
// Controller Number (Offset 106)
|
||||
buf[106] = ctrlIndex;
|
||||
|
||||
if (g_verbose) {
|
||||
printf("[MOCK] Page 0 filled for controller %d\n", ctrlIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void mock_fill_page1(int ctrlIndex) {
|
||||
unsigned char *buf = g_mockCtrl[ctrlIndex].bPage1Data;
|
||||
memset(buf, 0, 1024);
|
||||
|
||||
// RAID 0: Normal
|
||||
buf[0] = 0x01; // Status: Normal
|
||||
buf[1] = 0x05; // RAID Level: RAID 5
|
||||
buf[2] = 0x04; // Disk Count
|
||||
|
||||
// RAID 1: Normal
|
||||
buf[128] = 0x01;
|
||||
buf[129] = 0x06; // RAID 6
|
||||
buf[130] = 0x06; // 6 disks
|
||||
|
||||
if (g_verbose) {
|
||||
printf("[MOCK] Page 1 filled for controller %d\n", ctrlIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void mock_fill_page7(int ctrlIndex) {
|
||||
unsigned char *buf = g_mockCtrl[ctrlIndex].bPage7Data;
|
||||
memset(buf, 0, 512);
|
||||
|
||||
// LUN 0 mapping
|
||||
buf[0] = 0x00; // LUN 0
|
||||
buf[1] = 0x00; // RAID ID 0
|
||||
buf[2] = 0x01; // Enable
|
||||
|
||||
if (g_verbose) {
|
||||
printf("[MOCK] Page 7 filled for controller %d\n", ctrlIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void mock_fill_page10(int ctrlIndex) {
|
||||
unsigned char *buf = g_mockCtrl[ctrlIndex].bPage10Data;
|
||||
memset(buf, 0, 1024);
|
||||
|
||||
// RAID Info Block 0
|
||||
buf[0] = 0x01; // Status: Normal
|
||||
buf[1] = 0x05; // RAID 5
|
||||
buf[2] = 0x04; // 4 disks
|
||||
buf[3] = 0x00; // Stripe Size
|
||||
|
||||
// Capacity (Offset 8-15, 8 bytes)
|
||||
buf[8] = 0x00;
|
||||
buf[9] = 0x00;
|
||||
buf[10] = 0x00;
|
||||
buf[11] = 0x10; // 1 TB
|
||||
buf[12] = 0x00;
|
||||
buf[13] = 0x00;
|
||||
buf[14] = 0x00;
|
||||
buf[15] = 0x00;
|
||||
|
||||
if (g_verbose) {
|
||||
printf("[MOCK] Page 10 filled for controller %d\n", ctrlIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void mock_fill_page11(int ctrlIndex) {
|
||||
memset(g_mockCtrl[ctrlIndex].bPage11Data, 0, 1024);
|
||||
g_mockCtrl[ctrlIndex].bPage11Data[0] = 0x01;
|
||||
}
|
||||
|
||||
void mock_fill_page14(int ctrlIndex) {
|
||||
unsigned char *buf = g_mockCtrl[ctrlIndex].bPage14Data;
|
||||
memset(buf, 0, 1024);
|
||||
|
||||
// Event Log Structure (16 events, 64 bytes each)
|
||||
// Each event: 32 bytes data + 32 bytes message
|
||||
// Offset 0-15: Event Type (4 bytes), Timestamp (4 bytes), Slot/EID (4 bytes), Status (4 bytes)
|
||||
// Offset 16-31: Reserved
|
||||
// Offset 32-63: Event Message (32 bytes)
|
||||
|
||||
time_t now = time(NULL);
|
||||
|
||||
// Event 0: Controller started
|
||||
buf[0] = 0x01; // Event Type: System
|
||||
buf[1] = 0x00;
|
||||
buf[2] = 0x00;
|
||||
buf[3] = 0x00;
|
||||
*(unsigned int*)(buf + 4) = (unsigned int)(now - 3600); // 1 hour ago
|
||||
*(unsigned int*)(buf + 8) = 0xFFFFFFFF; // Controller
|
||||
buf[12] = 0x01; // Status: Info
|
||||
memcpy(buf + 32, "Controller started successfully ", 32);
|
||||
|
||||
// Event 1: Disk online
|
||||
buf[64] = 0x02; // Event Type: Disk
|
||||
buf[65] = 0x00;
|
||||
buf[66] = 0x00;
|
||||
buf[67] = 0x00;
|
||||
*(unsigned int*)(buf + 68) = (unsigned int)(now - 1800); // 30 min ago
|
||||
*(unsigned int*)(buf + 72) = 0x02; // Slot 2
|
||||
buf[76] = 0x01; // Status: Info
|
||||
memcpy(buf + 96, "Disk online ", 32);
|
||||
|
||||
// Event 2: RAID normal
|
||||
buf[128] = 0x03; // Event Type: RAID
|
||||
buf[129] = 0x00;
|
||||
buf[130] = 0x00;
|
||||
buf[131] = 0x00;
|
||||
*(unsigned int*)(buf + 132) = (unsigned int)(now - 900); // 15 min ago
|
||||
*(unsigned int*)(buf + 136) = 0x00; // RAID ID 0
|
||||
buf[140] = 0x01; // Status: Info
|
||||
memcpy(buf + 160, "RAID-01 status: Normal ", 32);
|
||||
|
||||
// Event 3: Temperature warning
|
||||
buf[192] = 0x02; // Event Type: Disk
|
||||
buf[193] = 0x00;
|
||||
buf[194] = 0x00;
|
||||
buf[195] = 0x00;
|
||||
*(unsigned int*)(buf + 196) = (unsigned int)(now - 600); // 10 min ago
|
||||
*(unsigned int*)(buf + 200) = 0x03; // Slot 3
|
||||
buf[204] = 0x02; // Status: Warning
|
||||
memcpy(buf + 224, "Disk temperature warning: 45C ", 32);
|
||||
|
||||
// Event 4: Power supply normal
|
||||
buf[256] = 0x05; // Event Type: Power
|
||||
buf[257] = 0x00;
|
||||
buf[258] = 0x00;
|
||||
buf[259] = 0x00;
|
||||
*(unsigned int*)(buf + 260) = (unsigned int)(now - 300); // 5 min ago
|
||||
*(unsigned int*)(buf + 264) = 0x00; // PS 0
|
||||
buf[268] = 0x01; // Status: Info
|
||||
memcpy(buf + 288, "Power supply 1 online ", 32);
|
||||
|
||||
// Event 5: Fan normal
|
||||
buf[320] = 0x04; // Event Type: Enclosure
|
||||
buf[321] = 0x00;
|
||||
buf[322] = 0x00;
|
||||
buf[323] = 0x00;
|
||||
*(unsigned int*)(buf + 324) = (unsigned int)(now - 120); // 2 min ago
|
||||
*(unsigned int*)(buf + 328) = 0x00; // Fan 0
|
||||
buf[332] = 0x01; // Status: Info
|
||||
memcpy(buf + 352, "Fan speed normal: 5200 RPM ", 32);
|
||||
|
||||
// Event 6: Network up
|
||||
buf[384] = 0x06; // Event Type: Network
|
||||
buf[385] = 0x00;
|
||||
buf[386] = 0x00;
|
||||
buf[387] = 0x00;
|
||||
*(unsigned int*)(buf + 388) = (unsigned int)(now - 60); // 1 min ago
|
||||
*(unsigned int*)(buf + 392) = 0x00; // Port 0
|
||||
buf[396] = 0x01; // Status: Info
|
||||
memcpy(buf + 416, "Network port link up ", 32);
|
||||
|
||||
// Event 7: Battery OK
|
||||
buf[448] = 0x01; // Event Type: System
|
||||
buf[449] = 0x00;
|
||||
buf[450] = 0x00;
|
||||
buf[451] = 0x00;
|
||||
*(unsigned int*)(buf + 452) = (unsigned int)now; // Now
|
||||
*(unsigned int*)(buf + 456) = 0xFFFFFFFF; // Controller
|
||||
buf[460] = 0x01; // Status: Info
|
||||
memcpy(buf + 480, "Battery backup unit initialized ", 32);
|
||||
|
||||
// Event count at offset 960 (0x3C0)
|
||||
buf[960] = 8; // 8 events stored
|
||||
|
||||
if (g_verbose) {
|
||||
printf("[MOCK] Page 14 filled for controller %d (8 events)\n", ctrlIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void mock_fill_page20(int ctrlIndex) {
|
||||
unsigned char *buf = g_mockCtrl[ctrlIndex].bPage20Data;
|
||||
memset(buf, 0, 512);
|
||||
|
||||
buf[0] = 2; // Fan Count
|
||||
buf[1] = 2; // Power Supply Count
|
||||
buf[2] = 4; // Temperature Sensor Count
|
||||
buf[3] = 1; // Heat Sink Count
|
||||
buf[4] = 1; // Power Supply Fan Count
|
||||
|
||||
buf[0x10] = 0x50;
|
||||
buf[0x11] = 0x14;
|
||||
buf[0x12] = 0x40;
|
||||
buf[0x13] = 0x13;
|
||||
|
||||
buf[0x18] = 0xA0;
|
||||
buf[0x19] = 0x0F;
|
||||
|
||||
buf[0x20] = 35;
|
||||
buf[0x21] = 32;
|
||||
buf[0x22] = 38;
|
||||
buf[0x23] = 30;
|
||||
|
||||
buf[0x40] = 0x00;
|
||||
buf[0x41] = 0x00;
|
||||
buf[0x42] = 0x01;
|
||||
buf[0x43] = 0x00;
|
||||
|
||||
buf[0x50] = 0x00;
|
||||
buf[0x51] = 0x00;
|
||||
|
||||
buf[0x60] = 0x01;
|
||||
buf[0x61] = 0x64;
|
||||
buf[0x62] = 0x00;
|
||||
|
||||
buf[0x00] = 0x00;
|
||||
|
||||
if (g_verbose) {
|
||||
printf("[MOCK] Page 20 filled for controller %d (Enclosure Info)\n", ctrlIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void mock_fill_page27(int ctrlIndex) {
|
||||
unsigned char *buf = g_mockCtrl[ctrlIndex].bPage27Data;
|
||||
memset(buf, 0, 512);
|
||||
|
||||
// Disk 0
|
||||
buf[0] = 0x00; // Slot 0
|
||||
buf[1] = 0x01; // Status: Online
|
||||
buf[2] = 0x00; // Type: HDD
|
||||
|
||||
// Disk 1
|
||||
buf[16] = 0x01; // Slot 1
|
||||
buf[17] = 0x01; // Status: Online
|
||||
|
||||
// Disk 2
|
||||
buf[32] = 0x02;
|
||||
buf[33] = 0x01;
|
||||
|
||||
// Disk 3
|
||||
buf[48] = 0x03;
|
||||
buf[49] = 0x01;
|
||||
|
||||
// Disk 4 (Spare)
|
||||
buf[64] = 0x04;
|
||||
buf[65] = 0x01;
|
||||
buf[66] = 0x02; // Spare
|
||||
|
||||
// Disk 5-7 Offline
|
||||
buf[80] = 0x05;
|
||||
buf[81] = 0x00; // Offline
|
||||
|
||||
if (g_verbose) {
|
||||
printf("[MOCK] Page 27 filled for controller %d\n", ctrlIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void mock_register_default_events(void) {
|
||||
// Disk Online Event (Slot 2)
|
||||
UCHAR diskOnline[] = {
|
||||
0x01, 0x00, 0x00, 0x00, // Event Type: Disk Online
|
||||
0x00, 0x00, 0x00, 0x00, // Timestamp
|
||||
0x02, 0x00, 0x00, 0x00, // Slot 2
|
||||
0x01, 0x00, 0x00, 0x00 // Status: OK
|
||||
};
|
||||
mock_push_event(diskOnline, 16);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Driver Functions
|
||||
// =============================================================================
|
||||
|
||||
UCHAR mock_DevIoControl(
|
||||
UCHAR bDevNo,
|
||||
ULONG lControlCode,
|
||||
ULONG lInSize,
|
||||
char *pInBuf,
|
||||
ULONG lOutSize,
|
||||
char *pOutBuf,
|
||||
USHORT *pusInBandErrCode) {
|
||||
|
||||
if (!g_bInitialized) mock_init();
|
||||
|
||||
if (bDevNo >= MAX_DTR_IN_HOST) {
|
||||
*pusInBandErrCode = 0xFF;
|
||||
return 0x01;
|
||||
}
|
||||
|
||||
// Handle different IOCTL codes
|
||||
switch (lControlCode) {
|
||||
case 0x9001: // GET_CONTROLLER_INFO
|
||||
if (g_verbose) {
|
||||
printf("[MOCK] IOCTL: GET_CONTROLLER_INFO ctrl=%d\n", bDevNo);
|
||||
}
|
||||
memcpy(pOutBuf, g_mockCtrl[bDevNo].bPage0Data,
|
||||
lOutSize > 1024 ? 1024 : lOutSize);
|
||||
break;
|
||||
|
||||
case 0x9002: // GET_RAID_INFO
|
||||
if (g_verbose) {
|
||||
printf("[MOCK] IOCTL: GET_RAID_INFO ctrl=%d\n", bDevNo);
|
||||
}
|
||||
memcpy(pOutBuf, g_mockCtrl[bDevNo].bPage1Data,
|
||||
lOutSize > 1024 ? 1024 : lOutSize);
|
||||
break;
|
||||
|
||||
case 0x9020: // GET_INBAND_EVENT
|
||||
if (g_verbose) {
|
||||
printf("[MOCK] IOCTL: GET_INBAND_EVENT ctrl=%d\n", bDevNo);
|
||||
}
|
||||
{
|
||||
EVENT_INFO evt;
|
||||
UCHAR result = mock_GetCtlrEvent(bDevNo, &evt);
|
||||
if (result == SYSOK) {
|
||||
memcpy(pOutBuf, evt.bEventBuf,
|
||||
lOutSize > 512 ? 512 : lOutSize);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (g_verbose) {
|
||||
printf("[MOCK] IOCTL: 0x%04lx (not handled, returning success)\n",
|
||||
lControlCode);
|
||||
}
|
||||
// Return success for unknown codes
|
||||
break;
|
||||
}
|
||||
|
||||
*pusInBandErrCode = 0x0000;
|
||||
return SYSOK;
|
||||
}
|
||||
|
||||
CHAR mock_QuaryDTR(UCHAR ucCtlrIndex, UCHAR *bPageBuf, UCHAR *pbHeatsink) {
|
||||
if (!g_bInitialized) mock_init();
|
||||
|
||||
if (ucCtlrIndex >= g_controllerCount) {
|
||||
return 0; // FALSE
|
||||
}
|
||||
|
||||
// Return controller info
|
||||
memcpy(bPageBuf, g_mockCtrl[ucCtlrIndex].bPage0Data, 1024);
|
||||
*pbHeatsink = 0x00;
|
||||
|
||||
if (g_verbose) {
|
||||
printf("[MOCK] QuaryDTR: controller %d, SN: %s\n",
|
||||
ucCtlrIndex, g_mockCtrl[ucCtlrIndex].bCtlrSN);
|
||||
}
|
||||
|
||||
return 1; // TRUE
|
||||
}
|
||||
|
||||
UCHAR mock_GetCtlrEvent(UCHAR bCtlrNo, EVENT_INFO *pEvent_info) {
|
||||
if (!g_bInitialized) mock_init();
|
||||
|
||||
if (g_mockEventCount <= 0) {
|
||||
pEvent_info->bStatus = 0;
|
||||
return 0x02; // SYSFAIL_EVENT_EMPTY
|
||||
}
|
||||
|
||||
// Get event from queue
|
||||
memcpy(pEvent_info->bEventBuf, g_mockEventQueue[g_mockEventHead], MAX_EDB_SZ);
|
||||
pEvent_info->bStatus = 1;
|
||||
|
||||
// Move head
|
||||
g_mockEventHead = (g_mockEventHead + 1) % 100;
|
||||
g_mockEventCount--;
|
||||
|
||||
if (g_verbose) {
|
||||
printf("[MOCK] GetCtlrEvent: event retrieved, remaining: %d\n",
|
||||
g_mockEventCount);
|
||||
}
|
||||
|
||||
return SYSOK;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Configuration Functions
|
||||
// =============================================================================
|
||||
|
||||
void mock_set_controller_count(int count) {
|
||||
if (count < 1) count = 1;
|
||||
if (count > MAX_DTR_IN_HOST) count = MAX_DTR_IN_HOST;
|
||||
g_controllerCount = count;
|
||||
}
|
||||
|
||||
void mock_set_controller_serial(int index, const char *sn) {
|
||||
if (index < 0 || index >= MAX_DTR_IN_HOST) return;
|
||||
memcpy(g_mockCtrl[index].bCtlrSN, sn, 16);
|
||||
}
|
||||
|
||||
void mock_set_controller_model(int index, const char *model) {
|
||||
if (index < 0 || index >= MAX_DTR_IN_HOST) return;
|
||||
memcpy(g_mockCtrl[index].bModelName, model, 32);
|
||||
}
|
||||
|
||||
void mock_set_controller_status(int index, UCHAR status) {
|
||||
if (index < 0 || index >= MAX_DTR_IN_HOST) return;
|
||||
g_mockCtrl[index].bStatus = status;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Event Simulation
|
||||
// =============================================================================
|
||||
|
||||
void mock_push_event(UCHAR *eventData, int length) {
|
||||
if (length > MAX_EDB_SZ) length = MAX_EDB_SZ;
|
||||
if (g_mockEventCount >= 100) {
|
||||
// Queue full, remove oldest
|
||||
g_mockEventHead = (g_mockEventHead + 1) % 100;
|
||||
g_mockEventCount--;
|
||||
}
|
||||
|
||||
memcpy(g_mockEventQueue[g_mockEventTail], eventData, length);
|
||||
g_mockEventTail = (g_mockEventTail + 1) % 100;
|
||||
g_mockEventCount++;
|
||||
|
||||
if (g_verbose) {
|
||||
printf("[MOCK] Event pushed, count: %d\n", g_mockEventCount);
|
||||
}
|
||||
}
|
||||
|
||||
void mock_clear_events(void) {
|
||||
g_mockEventHead = 0;
|
||||
g_mockEventTail = 0;
|
||||
g_mockEventCount = 0;
|
||||
|
||||
if (g_verbose) {
|
||||
printf("[MOCK] Events cleared\n");
|
||||
}
|
||||
}
|
||||
|
||||
void mock_generate_disk_event(int slot, int eventType) {
|
||||
UCHAR event[16];
|
||||
memset(event, 0, 16);
|
||||
|
||||
event[0] = eventType & 0xFF; // Event Type
|
||||
event[3] = slot & 0xFF; // Slot Number
|
||||
|
||||
mock_push_event(event, 16);
|
||||
}
|
||||
|
||||
void mock_generate_raid_event(int raidId, int eventType) {
|
||||
UCHAR event[16];
|
||||
memset(event, 0, 16);
|
||||
|
||||
event[0] = eventType & 0xFF; // Event Type
|
||||
event[3] = raidId & 0xFF; // RAID ID
|
||||
|
||||
mock_push_event(event, 16);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Configuration File (Stub)
|
||||
// =============================================================================
|
||||
|
||||
int mock_load_config(const char *filename) {
|
||||
// TODO: Parse JSON config file
|
||||
if (g_verbose) {
|
||||
printf("[MOCK] Config file loading not implemented: %s\n", filename);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mock_print_config(void) {
|
||||
printf("=== Mock Configuration ===\n");
|
||||
printf("Controller Count: %d\n", g_controllerCount);
|
||||
for (int i = 0; i < g_controllerCount; i++) {
|
||||
printf("Controller %d:\n", i);
|
||||
printf(" SN: %s\n", g_mockCtrl[i].bCtlrSN);
|
||||
printf(" Model: %s\n", g_mockCtrl[i].bModelName);
|
||||
printf(" Status: %s\n",
|
||||
g_mockCtrl[i].bStatus == CTLR_STATUS_ALIVE ? "Online" : "Offline");
|
||||
}
|
||||
printf("Pending Events: %d\n", g_mockEventCount);
|
||||
printf("=========================\n");
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Accessors for testing
|
||||
// =============================================================================
|
||||
|
||||
MOCK_CTRL_INFO* mock_get_ctrl_info(int index) {
|
||||
if (index < 0 || index >= MAX_DTR_IN_HOST) return NULL;
|
||||
return &g_mockCtrl[index];
|
||||
}
|
||||
|
||||
int mock_get_event_queue_count(void) {
|
||||
return g_mockEventCount;
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
#ifndef MOCK_DRIVER_H
|
||||
#define MOCK_DRIVER_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// Types
|
||||
typedef uint8_t UCHAR;
|
||||
typedef uint16_t USHORT;
|
||||
typedef uint32_t ULONG;
|
||||
typedef int8_t CHAR;
|
||||
typedef int SHORT;
|
||||
typedef int BOOL;
|
||||
|
||||
// Constants
|
||||
#define MAX_DTR_IN_HOST 4
|
||||
#define MAX_EVENT_NO 512
|
||||
#define MAX_EDB_SZ 512
|
||||
|
||||
// Status
|
||||
#define CTLR_STATUS_ALIVE 0x01
|
||||
#define CTLR_STATUS_DEAD 0x00
|
||||
|
||||
#define SYSOK 0x50
|
||||
#define SYSFAIL 0x40
|
||||
|
||||
// Mock Controller Info Structure
|
||||
typedef struct {
|
||||
UCHAR bStatus;
|
||||
UCHAR bCtlrSN[16];
|
||||
UCHAR bModelName[32];
|
||||
UCHAR bPad[16];
|
||||
UCHAR bPage0Data[1024];
|
||||
UCHAR bPage1Data[1024];
|
||||
UCHAR bPage7Data[512];
|
||||
UCHAR bPage10Data[1024];
|
||||
UCHAR bPage11Data[1024];
|
||||
UCHAR bPage14Data[1024];
|
||||
UCHAR bPage20Data[512];
|
||||
UCHAR bPage26Data[512];
|
||||
UCHAR bPage27Data[512];
|
||||
} MOCK_CTRL_INFO;
|
||||
|
||||
// Event Structure
|
||||
typedef struct {
|
||||
UCHAR bStatus;
|
||||
UCHAR bEventBuf[MAX_EDB_SZ];
|
||||
} EVENT_INFO;
|
||||
|
||||
// Function Declarations
|
||||
|
||||
// Initialization
|
||||
void mock_init(void);
|
||||
void mock_cleanup(void);
|
||||
|
||||
// Driver Functions
|
||||
UCHAR mock_DevIoControl(
|
||||
UCHAR bDevNo,
|
||||
ULONG lControlCode,
|
||||
ULONG lInSize,
|
||||
char *pInBuf,
|
||||
ULONG lOutSize,
|
||||
char *pOutBuf,
|
||||
USHORT *pusInBandErrCode);
|
||||
|
||||
CHAR mock_QuaryDTR(
|
||||
UCHAR ucCtlrIndex,
|
||||
UCHAR *bPageBuf,
|
||||
UCHAR *pbHeatsink);
|
||||
|
||||
UCHAR mock_GetCtlrEvent(
|
||||
UCHAR bCtlrNo,
|
||||
EVENT_INFO *pEvent_info);
|
||||
|
||||
// Configuration
|
||||
void mock_set_controller_count(int count);
|
||||
void mock_set_controller_serial(int index, const char *sn);
|
||||
void mock_set_controller_model(int index, const char *model);
|
||||
void mock_set_controller_status(int index, UCHAR status);
|
||||
void mock_set_verbose(bool verbose);
|
||||
bool mock_is_verbose(void);
|
||||
void mock_set_verbose(bool verbose);
|
||||
|
||||
// Event Simulation
|
||||
void mock_push_event(UCHAR *eventData, int length);
|
||||
void mock_clear_events(void);
|
||||
void mock_generate_disk_event(int slot, int eventType);
|
||||
void mock_generate_raid_event(int raidId, int eventType);
|
||||
|
||||
// Internal functions
|
||||
void mock_fill_page0(int ctrlIndex);
|
||||
void mock_fill_page1(int ctrlIndex);
|
||||
void mock_fill_page7(int ctrlIndex);
|
||||
void mock_fill_page10(int ctrlIndex);
|
||||
void mock_fill_page11(int ctrlIndex);
|
||||
void mock_fill_page14(int ctrlIndex);
|
||||
void mock_fill_page20(int ctrlIndex);
|
||||
void mock_fill_page27(int ctrlIndex);
|
||||
void mock_register_default_events(void);
|
||||
|
||||
// Configuration File
|
||||
int mock_load_config(const char *filename);
|
||||
void mock_print_config(void);
|
||||
|
||||
// Accessors for testing
|
||||
MOCK_CTRL_INFO* mock_get_ctrl_info(int index);
|
||||
int mock_get_event_queue_count(void);
|
||||
|
||||
#endif // MOCK_DRIVER_H
|
||||
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
* test_all_pages.c
|
||||
*
|
||||
* Test all mock driver pages
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "mock_driver.h"
|
||||
|
||||
void print_hex(const char *label, unsigned char *buf, int len) {
|
||||
printf("%s:\n", label);
|
||||
for (int i = 0; i < len; i++) {
|
||||
printf("%02X ", buf[i]);
|
||||
if ((i + 1) % 16 == 0) printf("\n");
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
void test_page0(void) {
|
||||
printf("\n========== TEST PAGE 0: Controller Info ==========\n");
|
||||
unsigned char buf[1024];
|
||||
unsigned char heatsink;
|
||||
|
||||
CHAR result = mock_QuaryDTR(0, buf, &heatsink);
|
||||
printf("Result: %d\n", result);
|
||||
|
||||
printf("Serial Number: %.16s\n", buf + 0);
|
||||
printf("Model Name: %.32s\n", buf + 16);
|
||||
printf("Firmware Version: %.8s\n", buf + 64);
|
||||
printf("BIOS Version: %.8s\n", buf + 72);
|
||||
printf("Status: 0x%02X\n", buf[96]);
|
||||
printf("RAID Ports: %d\n", buf[104]);
|
||||
printf("Disk Count: %d\n", buf[105]);
|
||||
printf("Controller #: %d\n", buf[106]);
|
||||
|
||||
print_hex("Full Page 0 (first 128 bytes)", buf, 128);
|
||||
}
|
||||
|
||||
void test_page1(void) {
|
||||
printf("\n========== TEST PAGE 1: RAID Snapshot ==========\n");
|
||||
|
||||
// Use accessor to get internal data
|
||||
MOCK_CTRL_INFO *ctrl = mock_get_ctrl_info(0);
|
||||
unsigned char *buf = ctrl->bPage1Data;
|
||||
|
||||
printf("RAID 0 Status: 0x%02X\n", buf[0]);
|
||||
printf("RAID 0 Level: 0x%02X\n", buf[1]);
|
||||
printf("RAID 0 Disk Count: %d\n", buf[2]);
|
||||
|
||||
printf("RAID 1 Status: 0x%02X\n", buf[128]);
|
||||
printf("RAID 1 Level: 0x%02X\n", buf[129]);
|
||||
printf("RAID 1 Disk Count: %d\n", buf[130]);
|
||||
|
||||
print_hex("Full Page 1 (first 256 bytes)", buf, 256);
|
||||
}
|
||||
|
||||
void test_page7(void) {
|
||||
printf("\n========== TEST PAGE 7: LUN Map Table ==========\n");
|
||||
|
||||
MOCK_CTRL_INFO *ctrl = mock_get_ctrl_info(0);
|
||||
unsigned char *buf = ctrl->bPage7Data;
|
||||
|
||||
printf("LUN 0: 0x%02X\n", buf[0]);
|
||||
printf("RAID ID 0: 0x%02X\n", buf[1]);
|
||||
printf("Enable: 0x%02X\n", buf[2]);
|
||||
|
||||
print_hex("Full Page 7", buf, 64);
|
||||
}
|
||||
|
||||
void test_page10(void) {
|
||||
printf("\n========== TEST PAGE 10: RAID Info 0-1 ==========\n");
|
||||
|
||||
MOCK_CTRL_INFO *ctrl = mock_get_ctrl_info(0);
|
||||
unsigned char *buf = ctrl->bPage10Data;
|
||||
|
||||
printf("RAID 0 Status: 0x%02X\n", buf[0]);
|
||||
printf("RAID 0 Level: 0x%02X\n", buf[1]);
|
||||
printf("RAID 0 Disks: %d\n", buf[2]);
|
||||
printf("RAID 0 Stripe: 0x%02X\n", buf[3]);
|
||||
|
||||
// Capacity (big-endian)
|
||||
unsigned long long capacity = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
capacity = (capacity << 8) | buf[8 + i];
|
||||
}
|
||||
printf("RAID 0 Capacity: %llu bytes\n", capacity);
|
||||
|
||||
print_hex("Full Page 10", buf, 128);
|
||||
}
|
||||
|
||||
void test_page11(void) {
|
||||
printf("\n========== TEST PAGE 11: RAID Info 2-3 ==========\n");
|
||||
|
||||
MOCK_CTRL_INFO *ctrl = mock_get_ctrl_info(0);
|
||||
unsigned char *buf = ctrl->bPage11Data;
|
||||
|
||||
printf("Status: 0x%02X\n", buf[0]);
|
||||
|
||||
print_hex("Full Page 11", buf, 64);
|
||||
}
|
||||
|
||||
void test_page14(void) {
|
||||
printf("\n========== TEST PAGE 14: Event Log ==========\n");
|
||||
|
||||
MOCK_CTRL_INFO *ctrl = mock_get_ctrl_info(0);
|
||||
unsigned char *buf = ctrl->bPage14Data;
|
||||
|
||||
printf("Event Count: %d\n", buf[960]);
|
||||
|
||||
// Print first event
|
||||
printf("\nEvent 0:\n");
|
||||
printf(" Type: 0x%02X\n", buf[0]);
|
||||
printf(" Timestamp: %u\n", *(unsigned int*)(buf + 4));
|
||||
printf(" Slot/EID: 0x%02X\n", *(unsigned int*)(buf + 8));
|
||||
printf(" Status: 0x%02X\n", buf[12]);
|
||||
printf(" Message: %.32s\n", buf + 32);
|
||||
|
||||
// Print second event
|
||||
printf("\nEvent 1:\n");
|
||||
printf(" Type: 0x%02X\n", buf[64]);
|
||||
printf(" Timestamp: %u\n", *(unsigned int*)(buf + 68));
|
||||
printf(" Slot/EID: 0x%02X\n", *(unsigned int*)(buf + 72));
|
||||
printf(" Status: 0x%02X\n", buf[76]);
|
||||
printf(" Message: %.32s\n", buf + 96);
|
||||
|
||||
print_hex("Page 14 (first 128 bytes)", buf, 128);
|
||||
}
|
||||
|
||||
void test_page20(void) {
|
||||
printf("\n========== TEST PAGE 20: Enclosure Info ==========\n");
|
||||
|
||||
MOCK_CTRL_INFO *ctrl = mock_get_ctrl_info(0);
|
||||
unsigned char *buf = ctrl->bPage20Data;
|
||||
|
||||
printf("Enclosure ID: %d\n", buf[0]);
|
||||
printf("Fan Count: %d\n", buf[1]);
|
||||
printf("Power Supply Count: %d\n", buf[2]);
|
||||
printf("Temperature Sensor Count: %d\n", buf[3]);
|
||||
printf("Heat Sink Count: %d\n", buf[4]);
|
||||
|
||||
// Fan speeds
|
||||
unsigned short fan1 = buf[0x10] | (buf[0x11] << 8);
|
||||
unsigned short fan2 = buf[0x12] | (buf[0x13] << 8);
|
||||
printf("Fan 1 Speed: %d RPM\n", fan1);
|
||||
printf("Fan 2 Speed: %d RPM\n", fan2);
|
||||
|
||||
// Temperatures
|
||||
printf("Temperature 1: %d C\n", buf[0x20]);
|
||||
printf("Temperature 2: %d C\n", buf[0x21]);
|
||||
printf("Temperature 3: %d C\n", buf[0x22]);
|
||||
printf("Temperature 4: %d C\n", buf[0x23]);
|
||||
|
||||
// Temp status
|
||||
printf("Temp 1 Status: %d\n", buf[0x40]);
|
||||
printf("Temp 2 Status: %d\n", buf[0x41]);
|
||||
printf("Temp 3 Status: %d\n", buf[0x42]);
|
||||
|
||||
// Power Supply status
|
||||
printf("Power Supply 1: %s\n", buf[0x50] == 0 ? "OK" : "FAIL");
|
||||
printf("Power Supply 2: %s\n", buf[0x51] == 0 ? "OK" : "FAIL");
|
||||
|
||||
// Battery
|
||||
printf("Battery Present: %s\n", buf[0x60] == 1 ? "Yes" : "No");
|
||||
printf("Battery Charge: %d%%\n", buf[0x61]);
|
||||
|
||||
print_hex("Full Page 20", buf, 128);
|
||||
}
|
||||
|
||||
void test_page27(void) {
|
||||
printf("\n========== TEST PAGE 27: Disk List ==========\n");
|
||||
|
||||
MOCK_CTRL_INFO *ctrl = mock_get_ctrl_info(0);
|
||||
unsigned char *buf = ctrl->bPage27Data;
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
int offset = i * 16;
|
||||
printf("Disk %d: Slot=0x%02X Status=0x%02X Type=0x%02X\n",
|
||||
i, buf[offset], buf[offset+1], buf[offset+2]);
|
||||
}
|
||||
|
||||
print_hex("Full Page 27", buf, 128);
|
||||
}
|
||||
|
||||
void test_devioctl(void) {
|
||||
printf("\n========== TEST DevIoControl ==========\n");
|
||||
|
||||
unsigned char outBuf[1024];
|
||||
USHORT errCode;
|
||||
UCHAR result;
|
||||
|
||||
// Test IOCTL 0x9001 - GET_CONTROLLER_INFO
|
||||
result = mock_DevIoControl(0, 0x9001, 0, NULL, 1024, (char*)outBuf, &errCode);
|
||||
printf("IOCTL 0x9001 (Controller Info): result=0x%02X err=0x%04X\n", result, errCode);
|
||||
printf(" SN: %.16s\n", outBuf);
|
||||
|
||||
// Test IOCTL 0x9002 - GET_RAID_INFO
|
||||
result = mock_DevIoControl(0, 0x9002, 0, NULL, 1024, (char*)outBuf, &errCode);
|
||||
printf("IOCTL 0x9002 (RAID Info): result=0x%02X err=0x%04X\n", result, errCode);
|
||||
printf(" RAID 0 Status: 0x%02X\n", outBuf[0]);
|
||||
|
||||
// Test IOCTL 0x9020 - GET_INBAND_EVENT
|
||||
result = mock_DevIoControl(0, 0x9020, 0, NULL, 512, (char*)outBuf, &errCode);
|
||||
printf("IOCTL 0x9020 (Event): result=0x%02X err=0x%04X\n", result, errCode);
|
||||
}
|
||||
|
||||
void test_events(void) {
|
||||
printf("\n========== TEST Events ==========\n");
|
||||
|
||||
// Generate various events
|
||||
mock_clear_events();
|
||||
|
||||
// Disk events
|
||||
mock_generate_disk_event(0, 0x01); // Online
|
||||
mock_generate_disk_event(1, 0x01); // Online
|
||||
mock_generate_disk_event(2, 0x02); // Offline
|
||||
mock_generate_disk_event(3, 0x03); // Error
|
||||
|
||||
// RAID events
|
||||
mock_generate_raid_event(0, 0x10); // Rebuild start
|
||||
mock_generate_raid_event(0, 0x11); // Rebuild done
|
||||
|
||||
printf("Generated 6 events\n");
|
||||
|
||||
// Read all events
|
||||
EVENT_INFO evt;
|
||||
int count = 0;
|
||||
while (mock_GetCtlrEvent(0, &evt) == SYSOK && evt.bStatus && count < 10) {
|
||||
printf("Event %d: Type=0x%02X Slot/ID=0x%02X\n",
|
||||
count + 1, evt.bEventBuf[0], evt.bEventBuf[3]);
|
||||
count++;
|
||||
}
|
||||
printf("Total events read: %d\n", count);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
printf("=== Mock Driver - All Pages Test ===\n");
|
||||
|
||||
// Enable verbose
|
||||
mock_set_verbose(true);
|
||||
|
||||
// Initialize
|
||||
mock_init();
|
||||
|
||||
// Run all tests
|
||||
test_page0();
|
||||
test_page1();
|
||||
test_page7();
|
||||
test_page10();
|
||||
test_page11();
|
||||
test_page14();
|
||||
test_page20();
|
||||
test_page27();
|
||||
test_devioctl();
|
||||
test_events();
|
||||
|
||||
// Cleanup
|
||||
mock_cleanup();
|
||||
|
||||
printf("\n=== All Tests Completed ===\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.apple.xcode.dsym.test_all_pages</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>dSYM</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
triple: 'arm64-apple-darwin'
|
||||
binary-path: test_all_pages
|
||||
relocations: []
|
||||
...
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* test_mock_driver.c
|
||||
*
|
||||
* Test program for mock driver
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "mock_driver.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
printf("=== Mock Driver Test ===\n\n");
|
||||
|
||||
// Enable verbose output
|
||||
mock_set_verbose(true);
|
||||
|
||||
// Initialize mock driver
|
||||
printf("\n--- Testing mock_init() ---\n");
|
||||
mock_init();
|
||||
|
||||
// Print configuration
|
||||
printf("\n--- Testing mock_print_config() ---\n");
|
||||
mock_print_config();
|
||||
|
||||
// Test QuaryDTR
|
||||
printf("\n--- Testing mock_QuaryDTR() ---\n");
|
||||
UCHAR pageBuf[1024];
|
||||
UCHAR heatsink;
|
||||
CHAR result = mock_QuaryDTR(0, pageBuf, &heatsink);
|
||||
printf("QuaryDTR result: %d\n", result);
|
||||
printf("Serial Number: %.16s\n", pageBuf);
|
||||
printf("Model Name: %.20s\n", pageBuf + 16);
|
||||
printf("Firmware: %.8s\n", pageBuf + 64);
|
||||
|
||||
// Test GetCtlrEvent
|
||||
printf("\n--- Testing mock_get_ctlr_event() ---\n");
|
||||
EVENT_INFO evt;
|
||||
result = mock_GetCtlrEvent(0, &evt);
|
||||
printf("GetCtlrEvent result: 0x%02X\n", result);
|
||||
printf("Event Status: %d\n", evt.bStatus);
|
||||
if (evt.bStatus) {
|
||||
printf("Event Data: ");
|
||||
for (int i = 0; i < 16 && i < MAX_EDB_SZ; i++) {
|
||||
printf("%02X ", evt.bEventBuf[i]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// Test pushing custom event
|
||||
printf("\n--- Testing mock_push_event() ---\n");
|
||||
UCHAR customEvent[] = {
|
||||
0x03, 0x00, 0x00, 0x00, // Event Type: RAID Rebuild Start
|
||||
0x00, 0x00, 0x00, 0x00, // Timestamp
|
||||
0x00, 0x00, 0x00, 0x00, // RAID ID 0
|
||||
0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
mock_push_event(customEvent, 16);
|
||||
printf("Custom event pushed\n");
|
||||
|
||||
// Get the custom event
|
||||
result = mock_GetCtlrEvent(0, &evt);
|
||||
printf("GetCtlrEvent result: 0x%02X\n", result);
|
||||
|
||||
// Test configuration functions
|
||||
printf("\n--- Testing configuration functions ---\n");
|
||||
mock_set_controller_count(2);
|
||||
mock_set_controller_serial(1, "MOCK00000002");
|
||||
mock_set_controller_model(1, "Accusys GEN3");
|
||||
mock_print_config();
|
||||
|
||||
// Test event generation
|
||||
printf("\n--- Testing event generation ---\n");
|
||||
mock_generate_disk_event(5, 0x01); // Disk 5 online
|
||||
mock_generate_raid_event(0, 0x02); // RAID 0 rebuild
|
||||
printf("Generated 2 events\n");
|
||||
|
||||
// Cleanup
|
||||
printf("\n--- Testing mock_cleanup() ---\n");
|
||||
mock_cleanup();
|
||||
|
||||
printf("\n=== All Tests Completed ===\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,272 +0,0 @@
|
||||
//! Application state and integration with Slint
|
||||
|
||||
use std::sync::Arc;
|
||||
use parking_lot::RwLock;
|
||||
use anyhow::Result;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::protocol::client::ServerClient;
|
||||
use crate::models::{Controller, RaidArray, Disk, Event};
|
||||
|
||||
pub struct AppState {
|
||||
pub client: Arc<RwLock<Option<ServerClient>>>,
|
||||
pub controllers: Arc<RwLock<Vec<Controller>>>,
|
||||
pub raids: Arc<RwLock<Vec<RaidArray>>>,
|
||||
pub disks: Arc<RwLock<Vec<Disk>>>,
|
||||
pub events: Arc<RwLock<Vec<Event>>>,
|
||||
pub selected_controller: Arc<RwLock<Option<i32>>>,
|
||||
pub selected_level: Arc<RwLock<String>>,
|
||||
pub connected: Arc<RwLock<bool>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Pagination {
|
||||
pub page: i32,
|
||||
pub page_size: i32,
|
||||
pub total_count: i32,
|
||||
pub total_pages: i32,
|
||||
pub has_next: bool,
|
||||
pub has_prev: bool,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Pagination {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where D: serde::Deserializer<'de> {
|
||||
#[derive(Deserialize)]
|
||||
struct Inner {
|
||||
page: i32,
|
||||
page_size: i32,
|
||||
total_count: i32,
|
||||
total_pages: i32,
|
||||
has_next: bool,
|
||||
has_prev: bool,
|
||||
}
|
||||
let inner = Inner::deserialize(deserializer)?;
|
||||
Ok(Pagination {
|
||||
page: inner.page,
|
||||
page_size: inner.page_size,
|
||||
total_count: inner.total_count,
|
||||
total_pages: inner.total_pages,
|
||||
has_next: inner.has_next,
|
||||
has_prev: inner.has_prev,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
client: Arc::new(RwLock::new(None)),
|
||||
controllers: Arc::new(RwLock::new(Vec::new())),
|
||||
raids: Arc::new(RwLock::new(Vec::new())),
|
||||
disks: Arc::new(RwLock::new(Vec::new())),
|
||||
events: Arc::new(RwLock::new(Vec::new())),
|
||||
selected_controller: Arc::new(RwLock::new(None)),
|
||||
selected_level: Arc::new(RwLock::new("all".to_string())),
|
||||
connected: Arc::new(RwLock::new(false)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn connect(&self, addr: &str) -> Result<()> {
|
||||
tracing::info!("=== AppState::connect() called with addr: {} ===", addr);
|
||||
let client = ServerClient::connect(addr).await?;
|
||||
tracing::info!("=== ServerClient connected, storing in AppState ===");
|
||||
*self.client.write() = Some(client);
|
||||
*self.connected.write() = true;
|
||||
tracing::info!("=== AppState connected flag set to true ===");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn refresh(&self) -> Result<()> {
|
||||
tracing::info!("=== refresh() called ===");
|
||||
|
||||
// Get client - clone it so we don't interfere with the stored one
|
||||
let client_opt = self.client.read().clone();
|
||||
tracing::info!("Got client option: {:?}", client_opt.is_some());
|
||||
|
||||
let client = match client_opt {
|
||||
Some(c) => c,
|
||||
None => return Err(anyhow::anyhow!("Not connected")),
|
||||
};
|
||||
|
||||
tracing::info!("Fetching controllers...");
|
||||
// Fetch all data
|
||||
let controllers_data = client.get_controllers().await?;
|
||||
tracing::info!("Got {} controllers from server", controllers_data.len());
|
||||
|
||||
let mut controllers = Vec::new();
|
||||
for v in controllers_data {
|
||||
if let Some(c) = Controller::from_json(&v) {
|
||||
controllers.push(c);
|
||||
}
|
||||
}
|
||||
tracing::info!("Parsed {} controllers", controllers.len());
|
||||
*self.controllers.write() = controllers;
|
||||
|
||||
tracing::info!("Fetching raids...");
|
||||
let raids_data = client.get_raids(None).await?;
|
||||
tracing::info!("Got {} raids from server", raids_data.len());
|
||||
|
||||
let mut raids = Vec::new();
|
||||
for v in raids_data {
|
||||
if let Some(r) = RaidArray::from_json(&v) {
|
||||
raids.push(r);
|
||||
}
|
||||
}
|
||||
tracing::info!("Parsed {} raids", raids.len());
|
||||
*self.raids.write() = raids;
|
||||
|
||||
tracing::info!("Fetching disks...");
|
||||
let disks_data = client.get_disks(None).await?;
|
||||
tracing::info!("Got {} disks from server", disks_data.len());
|
||||
let mut disks = Vec::new();
|
||||
for v in disks_data {
|
||||
if let Some(d) = Disk::from_json(&v) {
|
||||
disks.push(d);
|
||||
}
|
||||
}
|
||||
*self.disks.write() = disks;
|
||||
|
||||
let events_data = client.get_events(None, Some(50)).await?;
|
||||
let mut events = Vec::new();
|
||||
for v in events_data {
|
||||
if let Some(e) = Event::from_json(&v) {
|
||||
events.push(e);
|
||||
}
|
||||
}
|
||||
*self.events.write() = events;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_connected(&self) -> bool {
|
||||
*self.connected.read()
|
||||
}
|
||||
|
||||
pub fn get_controllers(&self) -> Vec<Controller> {
|
||||
self.controllers.read().clone()
|
||||
}
|
||||
|
||||
pub fn get_raids(&self) -> Vec<RaidArray> {
|
||||
self.raids.read().clone()
|
||||
}
|
||||
|
||||
pub fn get_disks(&self) -> Vec<Disk> {
|
||||
self.disks.read().clone()
|
||||
}
|
||||
|
||||
pub fn get_events(&self) -> Vec<Event> {
|
||||
self.events.read().clone()
|
||||
}
|
||||
|
||||
pub async fn load_events_page(&self, page: i32, page_size: i32) -> Result<Option<Pagination>> {
|
||||
let client = self.client.read().clone();
|
||||
let client = match client {
|
||||
Some(c) => c,
|
||||
None => return Err(anyhow::anyhow!("Not connected")),
|
||||
};
|
||||
|
||||
// Get selected level filter
|
||||
let level = self.selected_level.read().clone();
|
||||
let level_filter = if level == "all" { None } else { Some(level) };
|
||||
|
||||
// Fetch events with pagination
|
||||
let response = client.get_events_paginated(page, page_size, level_filter).await?;
|
||||
|
||||
let events_data = response.events;
|
||||
let pagination = response.pagination;
|
||||
|
||||
let mut events = Vec::new();
|
||||
for v in events_data {
|
||||
if let Some(e) = Event::from_json(&v) {
|
||||
events.push(e);
|
||||
}
|
||||
}
|
||||
*self.events.write() = events;
|
||||
|
||||
Ok(Some(pagination))
|
||||
}
|
||||
|
||||
pub async fn set_selected_level(&self, level: String) -> Result<()> {
|
||||
*self.selected_level.write() = level;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// RAID Operations
|
||||
pub async fn create_raid(&self, name: &str, level: u32, disks: Vec<u32>) -> Result<serde_json::Value> {
|
||||
let client = self.client.read().clone();
|
||||
let client = match client {
|
||||
Some(c) => c,
|
||||
None => return Err(anyhow::anyhow!("Not connected")),
|
||||
};
|
||||
client.create_raid(name, level, disks).await
|
||||
}
|
||||
|
||||
pub async fn delete_raid(&self, raid_id: u32) -> Result<serde_json::Value> {
|
||||
let client = self.client.read().clone();
|
||||
let client = match client {
|
||||
Some(c) => c,
|
||||
None => return Err(anyhow::anyhow!("Not connected")),
|
||||
};
|
||||
client.delete_raid(raid_id).await
|
||||
}
|
||||
|
||||
pub async fn rebuild_raid(&self, raid_id: u32) -> Result<serde_json::Value> {
|
||||
let client = self.client.read().clone();
|
||||
let client = match client {
|
||||
Some(c) => c,
|
||||
None => return Err(anyhow::anyhow!("Not connected")),
|
||||
};
|
||||
client.rebuild_raid(raid_id).await
|
||||
}
|
||||
|
||||
pub async fn initialize_disk(&self, disk_id: u32) -> Result<serde_json::Value> {
|
||||
let client = self.client.read().clone();
|
||||
let client = match client {
|
||||
Some(c) => c,
|
||||
None => return Err(anyhow::anyhow!("Not connected")),
|
||||
};
|
||||
client.initialize_disk(disk_id).await
|
||||
}
|
||||
|
||||
pub async fn locate_disk(&self, disk_id: u32) -> Result<serde_json::Value> {
|
||||
let client = self.client.read().clone();
|
||||
let client = match client {
|
||||
Some(c) => c,
|
||||
None => return Err(anyhow::anyhow!("Not connected")),
|
||||
};
|
||||
client.locate_disk(disk_id).await
|
||||
}
|
||||
|
||||
pub async fn remove_disk(&self, disk_id: u32, force: bool) -> Result<serde_json::Value> {
|
||||
let client = self.client.read().clone();
|
||||
let client = match client {
|
||||
Some(c) => c,
|
||||
None => return Err(anyhow::anyhow!("Not connected")),
|
||||
};
|
||||
client.remove_disk(disk_id, force).await
|
||||
}
|
||||
|
||||
pub async fn commit_config(&self) -> Result<serde_json::Value> {
|
||||
let client = self.client.read().clone();
|
||||
let client = match client {
|
||||
Some(c) => c,
|
||||
None => return Err(anyhow::anyhow!("Not connected")),
|
||||
};
|
||||
client.commit_config().await
|
||||
}
|
||||
|
||||
pub async fn connect_internal(addr: &str) -> Result<()> {
|
||||
let client = crate::protocol::client::ServerClient::connect(addr).await?;
|
||||
let mut this = Self::new();
|
||||
*this.client.write() = Some(client);
|
||||
*this.connected.write() = true;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AppState {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
//! RAIDGuard X GUI Client
|
||||
//!
|
||||
//! A cross-platform GUI client using Slint and TCP connection to the GUI server.
|
||||
|
||||
pub mod protocol;
|
||||
pub mod models;
|
||||
pub mod app;
|
||||
|
||||
pub use app::AppState;
|
||||
@@ -1,120 +0,0 @@
|
||||
//! RAIDGuard X GUI Client - Main entry point
|
||||
|
||||
use slint::{ComponentHandle, SharedString};
|
||||
|
||||
slint::include_modules!();
|
||||
|
||||
fn to_s(s: &str) -> SharedString {
|
||||
s.into()
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(
|
||||
tracing_subscriber::EnvFilter::from_default_env()
|
||||
.add_directive(tracing::Level::INFO.into()),
|
||||
)
|
||||
.init();
|
||||
|
||||
tracing::info!("Starting RAIDGuard X GUI...");
|
||||
|
||||
let app = AppWindow::new()?;
|
||||
|
||||
// Set initial demo data
|
||||
app.set_connected(true);
|
||||
app.set_status_text(to_s("Connected"));
|
||||
app.set_controller_count(1);
|
||||
app.set_raid_count(2);
|
||||
app.set_disk_count(4);
|
||||
app.set_auto_refresh(true);
|
||||
app.set_current_tab(0);
|
||||
|
||||
// Controller info
|
||||
app.set_ctrl1_name(to_s("RAID Controller"));
|
||||
app.set_ctrl1_ip(to_s("192.168.1.100"));
|
||||
app.set_ctrl1_status(to_s("Online"));
|
||||
app.set_ctrl1_model(to_s("Accusys RAID 9000"));
|
||||
app.set_ctrl1_firmware(to_s("v3.8.0"));
|
||||
app.set_ctrl1_sn(to_s("ACC123456789"));
|
||||
app.set_ctrl1_vendor(to_s("Accusys"));
|
||||
|
||||
// Controller 2 (empty)
|
||||
app.set_ctrl2_name(to_s("-"));
|
||||
app.set_ctrl2_ip(to_s("-"));
|
||||
app.set_ctrl2_status(to_s("-"));
|
||||
app.set_ctrl2_model(to_s("-"));
|
||||
app.set_ctrl2_firmware(to_s("-"));
|
||||
app.set_ctrl2_sn(to_s("-"));
|
||||
app.set_ctrl2_vendor(to_s("-"));
|
||||
|
||||
// RAID info
|
||||
app.set_raid1_name(to_s("RAID-01"));
|
||||
app.set_raid1_level(to_s("RAID 5"));
|
||||
app.set_raid1_status(to_s("Normal"));
|
||||
app.set_raid1_capacity(to_s("2.0 TB"));
|
||||
app.set_raid1_usage(to_s("45%"));
|
||||
|
||||
app.set_raid2_name(to_s("RAID-02"));
|
||||
app.set_raid2_level(to_s("RAID 6"));
|
||||
app.set_raid2_status(to_s("Normal"));
|
||||
app.set_raid2_capacity(to_s("4.0 TB"));
|
||||
app.set_raid2_usage(to_s("30%"));
|
||||
|
||||
// Disk info
|
||||
app.set_disk1_loc(to_s("Enclosure 0 Slot 0"));
|
||||
app.set_disk1_vendor(to_s("Seagate"));
|
||||
app.set_disk1_model(to_s("ST3000VX000"));
|
||||
app.set_disk1_capacity(to_s("3.0 TB"));
|
||||
app.set_disk1_status(to_s("Online"));
|
||||
|
||||
app.set_disk2_loc(to_s("Enclosure 0 Slot 1"));
|
||||
app.set_disk2_vendor(to_s("Seagate"));
|
||||
app.set_disk2_model(to_s("ST3000VX000"));
|
||||
app.set_disk2_capacity(to_s("3.0 TB"));
|
||||
app.set_disk2_status(to_s("Online"));
|
||||
|
||||
app.set_disk3_loc(to_s("Enclosure 0 Slot 2"));
|
||||
app.set_disk3_vendor(to_s("Seagate"));
|
||||
app.set_disk3_model(to_s("ST3000VX000"));
|
||||
app.set_disk3_capacity(to_s("3.0 TB"));
|
||||
app.set_disk3_status(to_s("Online"));
|
||||
|
||||
app.set_disk4_loc(to_s("Enclosure 0 Slot 3"));
|
||||
app.set_disk4_vendor(to_s("Seagate"));
|
||||
app.set_disk4_model(to_s("ST3000VX000"));
|
||||
app.set_disk4_capacity(to_s("3.0 TB"));
|
||||
app.set_disk4_status(to_s("Online"));
|
||||
|
||||
// Event info
|
||||
app.set_evtime1(to_s("2026/03/29-10:30:00"));
|
||||
app.set_evlevel1(to_s("Info"));
|
||||
app.set_evsource1(to_s("System"));
|
||||
app.set_evmsg1(to_s("Controller started successfully"));
|
||||
|
||||
app.set_evtime2(to_s("2026/03/29-10:25:00"));
|
||||
app.set_evlevel2(to_s("Info"));
|
||||
app.set_evsource2(to_s("Disk"));
|
||||
app.set_evmsg2(to_s("Disk online"));
|
||||
|
||||
app.set_evtime3(to_s("2026/03/29-10:20:00"));
|
||||
app.set_evlevel3(to_s("Warning"));
|
||||
app.set_evsource3(to_s("Enclosure"));
|
||||
app.set_evmsg3(to_s("Temperature warning"));
|
||||
|
||||
app.set_evtime4(to_s("2026/03/29-10:15:00"));
|
||||
app.set_evlevel4(to_s("Info"));
|
||||
app.set_evsource4(to_s("RAID"));
|
||||
app.set_evmsg4(to_s("RAID-01 status: Normal"));
|
||||
|
||||
app.set_evtime5(to_s("2026/03/29-10:10:00"));
|
||||
app.set_evlevel5(to_s("Info"));
|
||||
app.set_evsource5(to_s("Power"));
|
||||
app.set_evmsg5(to_s("Power supply online"));
|
||||
|
||||
tracing::info!("UI initialized, showing window...");
|
||||
|
||||
app.run()?;
|
||||
|
||||
tracing::info!("Application closed");
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
//! Data models for RAIDGuard X
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Controller {
|
||||
pub id: i32,
|
||||
pub ip: String,
|
||||
pub hostname: String,
|
||||
pub serial_number: String,
|
||||
pub model: String,
|
||||
pub firmware_version: String,
|
||||
pub status: String,
|
||||
pub enclosures: i32,
|
||||
pub total_disks: i32,
|
||||
}
|
||||
|
||||
impl Controller {
|
||||
pub fn from_json(value: &serde_json::Value) -> Option<Self> {
|
||||
Some(Self {
|
||||
id: value["id"].as_i64()? as i32,
|
||||
ip: value["ip"].as_str()?.to_string(),
|
||||
hostname: value["hostname"].as_str()?.to_string(),
|
||||
serial_number: value["serial_number"].as_str()?.to_string(),
|
||||
model: value["model"].as_str()?.to_string(),
|
||||
firmware_version: value["firmware_version"].as_str()?.to_string(),
|
||||
status: value["status"].as_str()?.to_string(),
|
||||
enclosures: value["enclosures"].as_i64().unwrap_or(0) as i32,
|
||||
total_disks: value["total_disks"].as_i64().unwrap_or(0) as i32,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_online(&self) -> bool {
|
||||
self.status == "online"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RaidArray {
|
||||
pub id: i32,
|
||||
pub controller_id: i32,
|
||||
pub name: String,
|
||||
pub raid_level: String,
|
||||
pub status: String,
|
||||
pub total_capacity_tb: f64,
|
||||
pub used_capacity_tb: f64,
|
||||
pub free_capacity_tb: f64,
|
||||
pub disk_count: i32,
|
||||
pub rebuild_progress: Option<i32>,
|
||||
}
|
||||
|
||||
impl RaidArray {
|
||||
pub fn from_json(value: &serde_json::Value) -> Option<Self> {
|
||||
Some(Self {
|
||||
id: value["id"].as_i64()? as i32,
|
||||
controller_id: value["controller_id"].as_i64().unwrap_or(0) as i32,
|
||||
name: value["name"].as_str()?.to_string(),
|
||||
raid_level: value["raid_level"].as_str()?.to_string(),
|
||||
status: value["status"].as_str()?.to_string(),
|
||||
total_capacity_tb: value["total_capacity_tb"].as_f64().unwrap_or(0.0),
|
||||
used_capacity_tb: value["used_capacity_tb"].as_f64().unwrap_or(0.0),
|
||||
free_capacity_tb: value["free_capacity_tb"].as_f64().unwrap_or(0.0),
|
||||
disk_count: value["disk_count"].as_i64().unwrap_or(0) as i32,
|
||||
rebuild_progress: value["rebuild_progress"].as_i64().map(|v| v as i32),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn usage_percent(&self) -> f64 {
|
||||
if self.total_capacity_tb > 0.0 {
|
||||
(self.used_capacity_tb / self.total_capacity_tb) * 100.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Disk {
|
||||
pub slot: i32,
|
||||
pub enclosure: i32,
|
||||
pub serial_number: String,
|
||||
pub model: String,
|
||||
pub vendor: String,
|
||||
pub capacity_tb: f64,
|
||||
pub status: String,
|
||||
pub disk_type: String,
|
||||
pub temperature: i32,
|
||||
pub array_id: Option<i32>,
|
||||
pub controller_id: i32,
|
||||
}
|
||||
|
||||
impl Disk {
|
||||
pub fn from_json(value: &serde_json::Value) -> Option<Self> {
|
||||
Some(Self {
|
||||
slot: value["slot"].as_i64().unwrap_or(0) as i32,
|
||||
enclosure: value["enclosure"].as_i64().unwrap_or(0) as i32,
|
||||
serial_number: value["serial_number"].as_str().unwrap_or("").to_string(),
|
||||
model: value["model"].as_str().unwrap_or("").to_string(),
|
||||
vendor: value["vendor"].as_str().unwrap_or("").to_string(),
|
||||
capacity_tb: value["capacity_tb"].as_f64().unwrap_or(0.0),
|
||||
status: value["status"].as_str().unwrap_or("").to_string(),
|
||||
disk_type: value["disk_type"].as_str().unwrap_or("").to_string(),
|
||||
temperature: value["temperature"].as_i64().unwrap_or(0) as i32,
|
||||
array_id: value["array_id"].as_i64().map(|v| v as i32),
|
||||
controller_id: value["controller_id"].as_i64().unwrap_or(0) as i32,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_online(&self) -> bool {
|
||||
self.status == "online"
|
||||
}
|
||||
|
||||
pub fn is_failed(&self) -> bool {
|
||||
self.status == "failed"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Event {
|
||||
pub id: i32,
|
||||
pub controller_id: i32,
|
||||
pub timestamp: i64,
|
||||
pub level: String,
|
||||
pub event_type: String,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl Event {
|
||||
pub fn from_json(value: &serde_json::Value) -> Option<Self> {
|
||||
Some(Self {
|
||||
id: value["id"].as_i64().unwrap_or(0) as i32,
|
||||
controller_id: value["controller_id"].as_i64().unwrap_or(0) as i32,
|
||||
timestamp: value["timestamp"].as_i64().unwrap_or(0),
|
||||
level: value["level"].as_str().unwrap_or("info").to_string(),
|
||||
event_type: value["event_type"].as_str().unwrap_or("").to_string(),
|
||||
message: value["message"].as_str().unwrap_or("").to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn formatted_time(&self) -> String {
|
||||
use chrono::{DateTime, Utc};
|
||||
let dt = DateTime::from_timestamp(self.timestamp, 0).unwrap_or_else(|| Utc::now());
|
||||
dt.format("%Y-%m-%d %H:%M:%S").to_string()
|
||||
}
|
||||
}
|
||||
@@ -1,275 +0,0 @@
|
||||
//! TCP Client for communicating with GUI Server
|
||||
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::sync::Mutex;
|
||||
use anyhow::Result;
|
||||
use std::sync::Arc;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::protocol::{Request, Response};
|
||||
use crate::app::Pagination;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct EventsResponse {
|
||||
pub events: Vec<serde_json::Value>,
|
||||
pub pagination: Pagination,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ServerClient {
|
||||
stream: Arc<Mutex<Option<TcpStream>>>,
|
||||
}
|
||||
|
||||
impl ServerClient {
|
||||
pub async fn connect(addr: &str) -> Result<Self> {
|
||||
tracing::info!("Connecting to server at {}...", addr);
|
||||
let stream = TcpStream::connect(addr).await?;
|
||||
stream.set_nodelay(true)?;
|
||||
tracing::info!("Connected to server");
|
||||
|
||||
Ok(Self {
|
||||
stream: Arc::new(Mutex::new(Some(stream))),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn send_request(&self, request: &Request) -> Result<Response> {
|
||||
let mut stream_guard = self.stream.lock().await;
|
||||
let stream = stream_guard.as_mut().ok_or_else(|| anyhow::anyhow!("Not connected"))?;
|
||||
|
||||
// Serialize and send request
|
||||
let json = serde_json::to_string(request)?;
|
||||
let mut send_buf = json.into_bytes();
|
||||
send_buf.push(b'\n');
|
||||
|
||||
stream.write_all(&send_buf).await?;
|
||||
|
||||
// Read response (read until newline)
|
||||
let mut response_buf = Vec::new();
|
||||
let mut byte = [0u8; 1];
|
||||
loop {
|
||||
let n = stream.read(&mut byte).await?;
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
if byte[0] == b'\n' {
|
||||
break;
|
||||
}
|
||||
response_buf.push(byte[0]);
|
||||
}
|
||||
let response_line = String::from_utf8(response_buf)?;
|
||||
|
||||
let response: Response = serde_json::from_str(&response_line.trim())?;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub async fn get_controllers(&self) -> Result<Vec<serde_json::Value>> {
|
||||
let request = Request::new("get_controllers");
|
||||
let response = self.send_request(&request).await?;
|
||||
|
||||
if !response.is_success() {
|
||||
return Err(anyhow::anyhow!("Error: {:?}", response.error));
|
||||
}
|
||||
|
||||
let data = response.data.ok_or_else(|| anyhow::anyhow!("No data in response"))?;
|
||||
let controllers = data["controllers"]
|
||||
.as_array()
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(controllers)
|
||||
}
|
||||
|
||||
pub async fn get_raids(&self, controller_id: Option<i32>) -> Result<Vec<serde_json::Value>> {
|
||||
let params = match controller_id {
|
||||
Some(id) => serde_json::json!({ "controller_id": id }),
|
||||
None => serde_json::json!({}),
|
||||
};
|
||||
let request = Request::with_params("get_raids", params);
|
||||
let response = self.send_request(&request).await?;
|
||||
|
||||
if !response.is_success() {
|
||||
return Err(anyhow::anyhow!("Error: {:?}", response.error));
|
||||
}
|
||||
|
||||
let data = response.data.ok_or_else(|| anyhow::anyhow!("No data in response"))?;
|
||||
let raids = data["raids"]
|
||||
.as_array()
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(raids)
|
||||
}
|
||||
|
||||
pub async fn get_disks(&self, controller_id: Option<i32>) -> Result<Vec<serde_json::Value>> {
|
||||
let params = match controller_id {
|
||||
Some(id) => serde_json::json!({ "controller_id": id }),
|
||||
None => serde_json::json!({}),
|
||||
};
|
||||
let request = Request::with_params("get_disks", params);
|
||||
let response = self.send_request(&request).await?;
|
||||
|
||||
if !response.is_success() {
|
||||
return Err(anyhow::anyhow!("Error: {:?}", response.error));
|
||||
}
|
||||
|
||||
let data = response.data.ok_or_else(|| anyhow::anyhow!("No data in response"))?;
|
||||
let disks = data["disks"]
|
||||
.as_array()
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(disks)
|
||||
}
|
||||
|
||||
pub async fn get_events(&self, controller_id: Option<i32>, limit: Option<usize>) -> Result<Vec<serde_json::Value>> {
|
||||
let mut params = serde_json::json!({});
|
||||
if let Some(id) = controller_id {
|
||||
params["controller_id"] = serde_json::json!(id);
|
||||
}
|
||||
if let Some(l) = limit {
|
||||
params["limit"] = serde_json::json!(l);
|
||||
}
|
||||
|
||||
let request = Request::with_params("get_events", params);
|
||||
let response = self.send_request(&request).await?;
|
||||
|
||||
if !response.is_success() {
|
||||
return Err(anyhow::anyhow!("Error: {:?}", response.error));
|
||||
}
|
||||
|
||||
let data = response.data.ok_or_else(|| anyhow::anyhow!("No data in response"))?;
|
||||
let events = data["events"]
|
||||
.as_array()
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(events)
|
||||
}
|
||||
|
||||
pub async fn get_events_paginated(&self, page: i32, page_size: i32, level: Option<String>) -> Result<EventsResponse> {
|
||||
let mut params = serde_json::json!({
|
||||
"page": page,
|
||||
"page_size": page_size
|
||||
});
|
||||
if let Some(level) = level {
|
||||
params["level"] = serde_json::json!(level);
|
||||
}
|
||||
|
||||
let request = Request::with_params("get_events", params);
|
||||
let response = self.send_request(&request).await?;
|
||||
|
||||
if !response.is_success() {
|
||||
return Err(anyhow::anyhow!("Error: {:?}", response.error));
|
||||
}
|
||||
|
||||
let data = response.data.ok_or_else(|| anyhow::anyhow!("No data in response"))?;
|
||||
let events_response: EventsResponse = serde_json::from_value(data)?;
|
||||
|
||||
Ok(events_response)
|
||||
}
|
||||
|
||||
pub async fn ping(&self) -> Result<bool> {
|
||||
let request = Request::new("ping");
|
||||
let response = self.send_request(&request).await?;
|
||||
Ok(response.is_success())
|
||||
}
|
||||
|
||||
pub fn is_connected(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
// RAID Operations
|
||||
pub async fn create_raid(&self, name: &str, level: u32, disks: Vec<u32>) -> Result<serde_json::Value> {
|
||||
let params = serde_json::json!({
|
||||
"name": name,
|
||||
"level": level,
|
||||
"disks": disks
|
||||
});
|
||||
let request = Request::with_params("create_raid", params);
|
||||
let response = self.send_request(&request).await?;
|
||||
|
||||
if !response.is_success() {
|
||||
return Err(anyhow::anyhow!("Error: {:?}", response.error));
|
||||
}
|
||||
|
||||
response.data.ok_or_else(|| anyhow::anyhow!("No data in response"))
|
||||
}
|
||||
|
||||
pub async fn delete_raid(&self, raid_id: u32) -> Result<serde_json::Value> {
|
||||
let params = serde_json::json!({ "raid_id": raid_id });
|
||||
let request = Request::with_params("delete_raid", params);
|
||||
let response = self.send_request(&request).await?;
|
||||
|
||||
if !response.is_success() {
|
||||
return Err(anyhow::anyhow!("Error: {:?}", response.error));
|
||||
}
|
||||
|
||||
response.data.ok_or_else(|| anyhow::anyhow!("No data in response"))
|
||||
}
|
||||
|
||||
pub async fn rebuild_raid(&self, raid_id: u32) -> Result<serde_json::Value> {
|
||||
let params = serde_json::json!({ "raid_id": raid_id });
|
||||
let request = Request::with_params("rebuild_raid", params);
|
||||
let response = self.send_request(&request).await?;
|
||||
|
||||
if !response.is_success() {
|
||||
return Err(anyhow::anyhow!("Error: {:?}", response.error));
|
||||
}
|
||||
|
||||
response.data.ok_or_else(|| anyhow::anyhow!("No data in response"))
|
||||
}
|
||||
|
||||
pub async fn initialize_disk(&self, disk_id: u32) -> Result<serde_json::Value> {
|
||||
let params = serde_json::json!({ "disk_id": disk_id });
|
||||
let request = Request::with_params("initialize_disk", params);
|
||||
let response = self.send_request(&request).await?;
|
||||
|
||||
if !response.is_success() {
|
||||
return Err(anyhow::anyhow!("Error: {:?}", response.error));
|
||||
}
|
||||
|
||||
response.data.ok_or_else(|| anyhow::anyhow!("No data in response"))
|
||||
}
|
||||
|
||||
pub async fn locate_disk(&self, disk_id: u32) -> Result<serde_json::Value> {
|
||||
let params = serde_json::json!({ "disk_id": disk_id });
|
||||
let request = Request::with_params("locate_disk", params);
|
||||
let response = self.send_request(&request).await?;
|
||||
|
||||
if !response.is_success() {
|
||||
return Err(anyhow::anyhow!("Error: {:?}", response.error));
|
||||
}
|
||||
|
||||
response.data.ok_or_else(|| anyhow::anyhow!("No data in response"))
|
||||
}
|
||||
|
||||
pub async fn remove_disk(&self, disk_id: u32, force: bool) -> Result<serde_json::Value> {
|
||||
let params = serde_json::json!({ "disk_id": disk_id, "force": force });
|
||||
let request = Request::with_params("remove_disk", params);
|
||||
let response = self.send_request(&request).await?;
|
||||
|
||||
if !response.is_success() {
|
||||
return Err(anyhow::anyhow!("Error: {:?}", response.error));
|
||||
}
|
||||
|
||||
response.data.ok_or_else(|| anyhow::anyhow!("No data in response"))
|
||||
}
|
||||
|
||||
pub async fn commit_config(&self) -> Result<serde_json::Value> {
|
||||
let request = Request::new("commit_config");
|
||||
let response = self.send_request(&request).await?;
|
||||
|
||||
if !response.is_success() {
|
||||
return Err(anyhow::anyhow!("Error: {:?}", response.error));
|
||||
}
|
||||
|
||||
response.data.ok_or_else(|| anyhow::anyhow!("No data in response"))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ServerClient {
|
||||
fn drop(&mut self) {
|
||||
tracing::info!("ServerClient dropped");
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
//! Protocol messages - shared with server
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Request {
|
||||
pub id: String,
|
||||
#[serde(rename = "type")]
|
||||
pub msg_type: String,
|
||||
pub action: String,
|
||||
#[serde(default)]
|
||||
pub params: serde_json::Value,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
pub fn new(action: impl Into<String>) -> Self {
|
||||
Self {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
msg_type: "request".to_string(),
|
||||
action: action.into(),
|
||||
params: serde_json::json!({}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_params(action: impl Into<String>, params: serde_json::Value) -> Self {
|
||||
Self {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
msg_type: "request".to_string(),
|
||||
action: action.into(),
|
||||
params,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Response {
|
||||
pub id: String,
|
||||
#[serde(rename = "type")]
|
||||
pub msg_type: String,
|
||||
pub status: String,
|
||||
#[serde(default)]
|
||||
pub data: Option<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
impl Response {
|
||||
pub fn is_success(&self) -> bool {
|
||||
self.status == "success"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Push {
|
||||
#[serde(rename = "type")]
|
||||
pub msg_type: String,
|
||||
pub event: String,
|
||||
#[serde(default)]
|
||||
pub data: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
pub mod actions {
|
||||
pub const GET_CONTROLLERS: &str = "get_controllers";
|
||||
pub const GET_CONTROLLER_INFO: &str = "get_controller_info";
|
||||
pub const GET_RAIDS: &str = "get_raids";
|
||||
pub const GET_DISKS: &str = "get_disks";
|
||||
pub const GET_EVENTS: &str = "get_events";
|
||||
pub const PING: &str = "ping";
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
//! Client protocol module
|
||||
|
||||
pub mod client;
|
||||
pub mod messages;
|
||||
|
||||
pub use client::*;
|
||||
pub use messages::*;
|
||||
@@ -0,0 +1,26 @@
|
||||
//! Simple test client
|
||||
|
||||
use std::io::{Read, Write};
|
||||
use std::net::TcpStream;
|
||||
|
||||
fn main() {
|
||||
println!("Connecting to 127.0.0.1:8923...");
|
||||
match TcpStream::connect("127.0.0.1:8923") {
|
||||
Ok(mut stream) => {
|
||||
println!("Connected! Sending ping...");
|
||||
let msg = r#"{"id":"test","type":"request","action":"ping","params":{}}"#;
|
||||
stream.write_all(msg.as_bytes()).unwrap();
|
||||
stream.write_all(b"\n").unwrap();
|
||||
|
||||
let mut buf = [0u8; 1024];
|
||||
match stream.read(&mut buf) {
|
||||
Ok(n) => {
|
||||
let response = String::from_utf8_lossy(&buf[..n]);
|
||||
println!("Response: {}", response);
|
||||
}
|
||||
Err(e) => println!("Read error: {}", e),
|
||||
}
|
||||
}
|
||||
Err(e) => println!("Connection failed: {}", e),
|
||||
}
|
||||
}
|
||||
@@ -1,659 +0,0 @@
|
||||
// RAIDGuard X - Complete GUI Recreation
|
||||
// Mimics Java Swing frmMain.java appearance
|
||||
|
||||
export component AppWindow inherits Window {
|
||||
title: "RAIDGuard X - DTR RAID Admin";
|
||||
width: 765px;
|
||||
height: 700px;
|
||||
background: #ece9d8;
|
||||
|
||||
// Native menu bar (macOS compatible - appears at top of screen)
|
||||
MenuBar {
|
||||
Menu {
|
||||
title: "File";
|
||||
MenuItem { title: "Load Controller List"; }
|
||||
MenuSeparator { }
|
||||
MenuItem { title: "Exit"; }
|
||||
}
|
||||
Menu {
|
||||
title: "Controller";
|
||||
MenuItem { title: "Add Controller"; }
|
||||
}
|
||||
Menu {
|
||||
title: "Tools";
|
||||
MenuItem { title: "Update Firmware"; }
|
||||
MenuItem { title: "Dump Controller Log"; }
|
||||
MenuSeparator { }
|
||||
MenuItem { title: "Email Settings"; }
|
||||
MenuItem { title: "Preferences"; }
|
||||
}
|
||||
Menu {
|
||||
title: "Help";
|
||||
MenuItem { title: "Local Help Center"; }
|
||||
MenuItem { title: "Play Tutorial"; }
|
||||
MenuSeparator { }
|
||||
MenuItem { title: "About"; }
|
||||
}
|
||||
}
|
||||
|
||||
// Callbacks
|
||||
callback connect_server();
|
||||
callback refresh_data();
|
||||
callback menu_about();
|
||||
callback menu_exit();
|
||||
callback menu_add_controller();
|
||||
|
||||
in-out property<bool> connected: false;
|
||||
in-out property<bool> is_loading: false;
|
||||
in-out property<string> status_text: "Disconnected";
|
||||
in-out property<int> current_tab: 0;
|
||||
|
||||
in-out property<int> controller_count: 0;
|
||||
in-out property<int> raid_count: 0;
|
||||
in-out property<int> disk_count: 0;
|
||||
in-out property<bool> auto_refresh: true;
|
||||
|
||||
in-out property<int> selected_controller_index: 0;
|
||||
in-out property<string> ctrl1_name: "RAID Controller";
|
||||
in-out property<string> ctrl1_ip: "192.168.1.100";
|
||||
in-out property<string> ctrl1_status: "Online";
|
||||
in-out property<string> ctrl1_model: "Accusys RAID 9000";
|
||||
in-out property<string> ctrl1_firmware: "v3.8.0";
|
||||
in-out property<string> ctrl1_sn: "ACC123456789";
|
||||
in-out property<string> ctrl1_vendor: "Accusys";
|
||||
in-out property<string> ctrl2_name: "-";
|
||||
in-out property<string> ctrl2_ip: "-";
|
||||
in-out property<string> ctrl2_status: "-";
|
||||
in-out property<string> ctrl2_model: "-";
|
||||
in-out property<string> ctrl2_firmware: "-";
|
||||
in-out property<string> ctrl2_sn: "-";
|
||||
in-out property<string> ctrl2_vendor: "-";
|
||||
|
||||
in-out property<string> raid1_name: "RAID-01";
|
||||
in-out property<string> raid1_level: "RAID 5";
|
||||
in-out property<string> raid1_status: "Normal";
|
||||
in-out property<string> raid1_capacity: "2.0 TB";
|
||||
in-out property<string> raid1_usage: "45%";
|
||||
in-out property<float> raid1_usage_pct: 45.0;
|
||||
in-out property<string> raid2_name: "RAID-02";
|
||||
in-out property<string> raid2_level: "RAID 6";
|
||||
in-out property<string> raid2_status: "Normal";
|
||||
in-out property<string> raid2_capacity: "4.0 TB";
|
||||
in-out property<string> raid2_usage: "30%";
|
||||
in-out property<float> raid2_usage_pct: 30.0;
|
||||
|
||||
in-out property<string> disk1_loc: "Enclosure 0 Slot 0";
|
||||
in-out property<string> disk1_model: "ST3000VX000";
|
||||
in-out property<string> disk1_status: "Online";
|
||||
in-out property<string> disk1_capacity: "3.0 TB";
|
||||
in-out property<string> disk1_vendor: "Seagate";
|
||||
in-out property<string> disk2_loc: "Enclosure 0 Slot 1";
|
||||
in-out property<string> disk2_model: "ST3000VX000";
|
||||
in-out property<string> disk2_status: "Online";
|
||||
in-out property<string> disk2_capacity: "3.0 TB";
|
||||
in-out property<string> disk2_vendor: "Seagate";
|
||||
in-out property<string> disk3_loc: "Enclosure 0 Slot 2";
|
||||
in-out property<string> disk3_model: "ST3000VX000";
|
||||
in-out property<string> disk3_status: "Online";
|
||||
in-out property<string> disk3_capacity: "3.0 TB";
|
||||
in-out property<string> disk3_vendor: "Seagate";
|
||||
in-out property<string> disk4_loc: "Enclosure 0 Slot 3";
|
||||
in-out property<string> disk4_model: "ST3000VX000";
|
||||
in-out property<string> disk4_status: "Online";
|
||||
in-out property<string> disk4_capacity: "3.0 TB";
|
||||
in-out property<string> disk4_vendor: "Seagate";
|
||||
|
||||
in-out property<string> evtime1: "2026/03/29-10:30:00";
|
||||
in-out property<string> evlevel1: "Info";
|
||||
in-out property<string> evmsg1: "Controller started successfully";
|
||||
in-out property<string> evsource1: "System";
|
||||
in-out property<string> evtime2: "2026/03/29-10:25:00";
|
||||
in-out property<string> evlevel2: "Info";
|
||||
in-out property<string> evmsg2: "Disk online";
|
||||
in-out property<string> evsource2: "Disk";
|
||||
in-out property<string> evtime3: "2026/03/29-10:20:00";
|
||||
in-out property<string> evlevel3: "Warning";
|
||||
in-out property<string> evmsg3: "Temperature warning";
|
||||
in-out property<string> evsource3: "Enclosure";
|
||||
in-out property<string> evtime4: "2026/03/29-10:15:00";
|
||||
in-out property<string> evlevel4: "Info";
|
||||
in-out property<string> evmsg4: "RAID-01 status: Normal";
|
||||
in-out property<string> evsource4: "RAID";
|
||||
in-out property<string> evtime5: "2026/03/29-10:10:00";
|
||||
in-out property<string> evlevel5: "Info";
|
||||
in-out property<string> evmsg5: "Power supply online";
|
||||
in-out property<string> evsource5: "Power";
|
||||
|
||||
// Main layout
|
||||
VerticalLayout {
|
||||
spacing: 0;
|
||||
|
||||
// Toolbar - matches Java JToolBar (750x65)
|
||||
Rectangle {
|
||||
height: 65px;
|
||||
background: #d4d0c8;
|
||||
HorizontalLayout {
|
||||
spacing: 2px;
|
||||
|
||||
// Add button - matches jbtAddSystem
|
||||
Rectangle {
|
||||
width: 70px;
|
||||
height: 50px;
|
||||
VerticalLayout {
|
||||
spacing: 0;
|
||||
Image {
|
||||
source: @image-url("../icons/func-additem.png");
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
Text { text: "Add"; font_size: 11px; font_weight: 700; color: #000000; }
|
||||
}
|
||||
}
|
||||
|
||||
// Remove button
|
||||
Rectangle {
|
||||
width: 70px;
|
||||
height: 50px;
|
||||
VerticalLayout {
|
||||
spacing: 0;
|
||||
Image {
|
||||
source: @image-url("../icons/func-deleteitem.png");
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
Text { text: "Remove"; font_size: 11px; font_weight: 700; color: #000000; }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { width: 8px; }
|
||||
|
||||
// Create button - matches jbtCreateArray
|
||||
Rectangle {
|
||||
width: 70px;
|
||||
height: 50px;
|
||||
VerticalLayout {
|
||||
spacing: 0;
|
||||
Image {
|
||||
source: @image-url("../icons/func-createarray.png");
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
Text { text: "Create"; font_size: 11px; font_weight: 700; color: #000000; }
|
||||
}
|
||||
}
|
||||
|
||||
// Delete button - matches jbtDeleteArray
|
||||
Rectangle {
|
||||
width: 70px;
|
||||
height: 50px;
|
||||
VerticalLayout {
|
||||
spacing: 0;
|
||||
Image {
|
||||
source: @image-url("../icons/func-deletearray.png");
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
Text { text: "Delete"; font_size: 11px; font_weight: 700; color: #000000; }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { width: 8px; }
|
||||
|
||||
// Email button - matches jbtEmail
|
||||
Rectangle {
|
||||
width: 70px;
|
||||
height: 50px;
|
||||
VerticalLayout {
|
||||
spacing: 0;
|
||||
Image {
|
||||
source: @image-url("../icons/func-email.png");
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
Text { text: "Email"; font_size: 11px; font_weight: 700; color: #000000; }
|
||||
}
|
||||
}
|
||||
|
||||
// Settings button - matches jbtSettings
|
||||
Rectangle {
|
||||
width: 70px;
|
||||
height: 50px;
|
||||
VerticalLayout {
|
||||
spacing: 0;
|
||||
Image {
|
||||
source: @image-url("../icons/func-settings.png");
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
Text { text: "Settings"; font_size: 11px; font_weight: 700; color: #000000; }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { width: 1px; }
|
||||
}
|
||||
}
|
||||
|
||||
// Separator line
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: #a0a0a0;
|
||||
}
|
||||
|
||||
// Main Content Area
|
||||
HorizontalLayout {
|
||||
spacing: 0;
|
||||
height: 1px;
|
||||
|
||||
// Left Panel - Host List (matches jpHostList ~220px)
|
||||
Rectangle {
|
||||
width: 220px;
|
||||
background: #ffffff;
|
||||
VerticalLayout {
|
||||
padding: 0;
|
||||
spacing: 0;
|
||||
|
||||
// Header - matches jlHostList
|
||||
Rectangle {
|
||||
height: 22px;
|
||||
background: #d4d0c8;
|
||||
Text {
|
||||
text: "Host List";
|
||||
font_size: 12px;
|
||||
font_weight: 700;
|
||||
color: #000000;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
// Table Header - matches jtHostList header
|
||||
Rectangle {
|
||||
height: 20px;
|
||||
background: #d4d0c8;
|
||||
HorizontalLayout {
|
||||
spacing: 2px;
|
||||
Text { text: "Host"; font_size: 12px; font_weight: 700; width: 65px; }
|
||||
Text { text: "IP"; font_size: 12px; font_weight: 700; width: 75px; }
|
||||
Text { text: "Status"; font_size: 12px; font_weight: 700; width: 50px; }
|
||||
}
|
||||
}
|
||||
|
||||
// Controller Row 1 - matches jtHostList row
|
||||
Rectangle {
|
||||
height: 20px;
|
||||
background: #d4d0c8;
|
||||
HorizontalLayout {
|
||||
spacing: 2px;
|
||||
Text { text: ctrl1_name; font_size: 12px; width: 65px; }
|
||||
Text { text: ctrl1_ip; font_size: 12px; width: 75px; }
|
||||
Text { text: ctrl1_status; font_size: 12px; color: #008000; width: 50px; }
|
||||
}
|
||||
}
|
||||
|
||||
// Controller Row 2
|
||||
Rectangle {
|
||||
height: 20px;
|
||||
background: #ffffff;
|
||||
HorizontalLayout {
|
||||
spacing: 2px;
|
||||
Text { text: ctrl2_name; font_size: 12px; width: 65px; }
|
||||
Text { text: ctrl2_ip; font_size: 12px; width: 75px; }
|
||||
Text { text: ctrl2_status; font_size: 12px; width: 50px; }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { }
|
||||
}
|
||||
}
|
||||
|
||||
// Vertical separator
|
||||
Rectangle {
|
||||
width: 1px;
|
||||
background: #a0a0a0;
|
||||
}
|
||||
|
||||
// Right Panel - Tabs
|
||||
VerticalLayout {
|
||||
spacing: 0;
|
||||
|
||||
// Tab Header - matches JTabbedPane
|
||||
Rectangle {
|
||||
height: 24px;
|
||||
background: #d4d0c8;
|
||||
HorizontalLayout {
|
||||
spacing: 0;
|
||||
|
||||
// Controller Tab
|
||||
Rectangle {
|
||||
width: 100px;
|
||||
background: current_tab == 0 ? #ffffff : #d4d0c8;
|
||||
Text {
|
||||
text: "Controller";
|
||||
font_size: 12px;
|
||||
font_weight: current_tab == 0 ? 700 : 400;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
// RAID Tab
|
||||
Rectangle {
|
||||
width: 80px;
|
||||
background: current_tab == 1 ? #ffffff : #d4d0c8;
|
||||
Text {
|
||||
text: "RAID";
|
||||
font_size: 12px;
|
||||
font_weight: current_tab == 1 ? 700 : 400;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
// Drives Tab
|
||||
Rectangle {
|
||||
width: 80px;
|
||||
background: current_tab == 2 ? #ffffff : #d4d0c8;
|
||||
Text {
|
||||
text: "Drives";
|
||||
font_size: 12px;
|
||||
font_weight: current_tab == 2 ? 700 : 400;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
// Event Tab
|
||||
Rectangle {
|
||||
width: 80px;
|
||||
background: current_tab == 3 ? #ffffff : #d4d0c8;
|
||||
Text {
|
||||
text: "Event";
|
||||
font_size: 12px;
|
||||
font_weight: current_tab == 3 ? 700 : 400;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tab Content
|
||||
Rectangle {
|
||||
background: #ffffff;
|
||||
height: 1px;
|
||||
|
||||
// Controller Tab - matches jpController layout
|
||||
if current_tab == 0: VerticalLayout {
|
||||
padding: 10px;
|
||||
spacing: 8px;
|
||||
|
||||
Rectangle {
|
||||
background: #ffffff;
|
||||
VerticalLayout {
|
||||
spacing: 4px;
|
||||
Text { text: "Controller Information"; font_size: 12px; font_weight: 700; }
|
||||
Rectangle { height: 8px; }
|
||||
HorizontalLayout {
|
||||
spacing: 20px;
|
||||
VerticalLayout { spacing: 3px;
|
||||
Text { text: "Controller Name:"; font_size: 12px; font_weight: 700; horizontal-alignment: right; }
|
||||
Text { text: "Model Name:"; font_size: 12px; font_weight: 700; horizontal-alignment: right; }
|
||||
Text { text: "Serial Number:"; font_size: 12px; font_weight: 700; horizontal-alignment: right; }
|
||||
Text { text: "Firmware Version:"; font_size: 12px; font_weight: 700; horizontal-alignment: right; }
|
||||
Text { text: "BIOS Version:"; font_size: 12px; font_weight: 700; horizontal-alignment: right; }
|
||||
Text { text: "Status:"; font_size: 12px; font_weight: 700; horizontal-alignment: right; }
|
||||
}
|
||||
VerticalLayout { spacing: 3px;
|
||||
Text { text: ctrl1_name; font_size: 12px; }
|
||||
Text { text: ctrl1_model; font_size: 12px; }
|
||||
Text { text: ctrl1_sn; font_size: 12px; }
|
||||
Text { text: ctrl1_firmware; font_size: 12px; }
|
||||
Text { text: "1.2.3.4"; font_size: 12px; }
|
||||
Text { text: ctrl1_status; font_size: 12px; color: #008000; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RAID Tab
|
||||
if current_tab == 1: VerticalLayout {
|
||||
padding: 10px;
|
||||
spacing: 8px;
|
||||
|
||||
Rectangle {
|
||||
background: #ffffff;
|
||||
VerticalLayout {
|
||||
spacing: 4px;
|
||||
HorizontalLayout {
|
||||
Text { text: raid1_name; font_size: 12px; font_weight: 700; }
|
||||
Text { text: raid1_status; font_size: 12px; color: #008000; }
|
||||
}
|
||||
HorizontalLayout {
|
||||
spacing: 20px;
|
||||
VerticalLayout { spacing: 3px;
|
||||
Text { text: "RAID Level:"; font_size: 12px; font_weight: 700; horizontal-alignment: right; }
|
||||
Text { text: "Capacity:"; font_size: 12px; font_weight: 700; horizontal-alignment: right; }
|
||||
Text { text: "Usage:"; font_size: 12px; font_weight: 700; horizontal-alignment: right; }
|
||||
}
|
||||
VerticalLayout { spacing: 3px;
|
||||
Text { text: raid1_level; font_size: 12px; }
|
||||
Text { text: raid1_capacity; font_size: 12px; }
|
||||
Text { text: raid1_usage; font_size: 12px; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
background: #ffffff;
|
||||
VerticalLayout {
|
||||
spacing: 4px;
|
||||
HorizontalLayout {
|
||||
Text { text: raid2_name; font_size: 12px; font_weight: 700; }
|
||||
Text { text: raid2_status; font_size: 12px; color: #008000; }
|
||||
}
|
||||
HorizontalLayout {
|
||||
spacing: 20px;
|
||||
VerticalLayout { spacing: 3px;
|
||||
Text { text: "RAID Level:"; font_size: 12px; font_weight: 700; horizontal-alignment: right; }
|
||||
Text { text: "Capacity:"; font_size: 12px; font_weight: 700; horizontal-alignment: right; }
|
||||
Text { text: "Usage:"; font_size: 12px; font_weight: 700; horizontal-alignment: right; }
|
||||
}
|
||||
VerticalLayout { spacing: 3px;
|
||||
Text { text: raid2_level; font_size: 12px; }
|
||||
Text { text: raid2_capacity; font_size: 12px; }
|
||||
Text { text: raid2_usage; font_size: 12px; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Drives Tab
|
||||
if current_tab == 2: VerticalLayout {
|
||||
padding: 4px;
|
||||
spacing: 2px;
|
||||
|
||||
Rectangle {
|
||||
height: 20px;
|
||||
background: #d4d0c8;
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
Text { text: "Location"; font_size: 12px; font_weight: 700; width: 120px; }
|
||||
Text { text: "Vendor"; font_size: 12px; font_weight: 700; width: 80px; }
|
||||
Text { text: "Model"; font_size: 12px; font_weight: 700; width: 140px; }
|
||||
Text { text: "Capacity"; font_size: 12px; font_weight: 700; width: 70px; }
|
||||
Text { text: "Status"; font_size: 12px; font_weight: 700; width: 60px; }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 20px;
|
||||
background: #d4d0c8;
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
Text { text: disk1_loc; font_size: 12px; width: 120px; }
|
||||
Text { text: disk1_vendor; font_size: 12px; width: 80px; }
|
||||
Text { text: disk1_model; font_size: 12px; width: 140px; }
|
||||
Text { text: disk1_capacity; font_size: 12px; width: 70px; }
|
||||
Text { text: disk1_status; font_size: 12px; color: #008000; width: 60px; }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 20px;
|
||||
background: #ffffff;
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
Text { text: disk2_loc; font_size: 12px; width: 120px; }
|
||||
Text { text: disk2_vendor; font_size: 12px; width: 80px; }
|
||||
Text { text: disk2_model; font_size: 12px; width: 140px; }
|
||||
Text { text: disk2_capacity; font_size: 12px; width: 70px; }
|
||||
Text { text: disk2_status; font_size: 12px; color: #008000; width: 60px; }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 20px;
|
||||
background: #d4d0c8;
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
Text { text: disk3_loc; font_size: 12px; width: 120px; }
|
||||
Text { text: disk3_vendor; font_size: 12px; width: 80px; }
|
||||
Text { text: disk3_model; font_size: 12px; width: 140px; }
|
||||
Text { text: disk3_capacity; font_size: 12px; width: 70px; }
|
||||
Text { text: disk3_status; font_size: 12px; color: #008000; width: 60px; }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 20px;
|
||||
background: #ffffff;
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
Text { text: disk4_loc; font_size: 12px; width: 120px; }
|
||||
Text { text: disk4_vendor; font_size: 12px; width: 80px; }
|
||||
Text { text: disk4_model; font_size: 12px; width: 140px; }
|
||||
Text { text: disk4_capacity; font_size: 12px; width: 70px; }
|
||||
Text { text: disk4_status; font_size: 12px; color: #008000; width: 60px; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Event Tab
|
||||
if current_tab == 3: VerticalLayout {
|
||||
padding: 4px;
|
||||
spacing: 2px;
|
||||
|
||||
Rectangle {
|
||||
height: 20px;
|
||||
background: #d4d0c8;
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
Text { text: "Date/Time"; font_size: 12px; font_weight: 700; width: 130px; }
|
||||
Text { text: "Level"; font_size: 12px; font_weight: 700; width: 50px; }
|
||||
Text { text: "Source"; font_size: 12px; font_weight: 700; width: 60px; }
|
||||
Text { text: "Message"; font_size: 12px; font_weight: 700; width: 300px; }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 18px;
|
||||
background: #d4d0c8;
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
Text { text: evtime1; font_size: 12px; width: 130px; }
|
||||
Text { text: evlevel1; font_size: 12px; color: #008000; width: 50px; }
|
||||
Text { text: evsource1; font_size: 12px; width: 60px; }
|
||||
Text { text: evmsg1; font_size: 12px; width: 300px; }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 18px;
|
||||
background: #ffffff;
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
Text { text: evtime2; font_size: 12px; width: 130px; }
|
||||
Text { text: evlevel2; font_size: 12px; color: #008000; width: 50px; }
|
||||
Text { text: evsource2; font_size: 12px; width: 60px; }
|
||||
Text { text: evmsg2; font_size: 12px; width: 300px; }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 18px;
|
||||
background: #d4d0c8;
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
Text { text: evtime3; font_size: 12px; width: 130px; }
|
||||
Text { text: evlevel3; font_size: 12px; color: #ffa500; width: 50px; }
|
||||
Text { text: evsource3; font_size: 12px; width: 60px; }
|
||||
Text { text: evmsg3; font_size: 12px; width: 300px; }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 18px;
|
||||
background: #ffffff;
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
Text { text: evtime4; font_size: 12px; width: 130px; }
|
||||
Text { text: evlevel4; font_size: 12px; color: #008000; width: 50px; }
|
||||
Text { text: evsource4; font_size: 12px; width: 60px; }
|
||||
Text { text: evmsg4; font_size: 12px; width: 300px; }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 18px;
|
||||
background: #d4d0c8;
|
||||
HorizontalLayout {
|
||||
spacing: 8px;
|
||||
Text { text: evtime5; font_size: 12px; width: 130px; }
|
||||
Text { text: evlevel5; font_size: 12px; color: #008000; width: 50px; }
|
||||
Text { text: evsource5; font_size: 12px; width: 60px; }
|
||||
Text { text: evmsg5; font_size: 12px; width: 300px; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Status Bar - matches Java status bar
|
||||
Rectangle {
|
||||
height: 22px;
|
||||
background: #d4d0c8;
|
||||
HorizontalLayout {
|
||||
spacing: 16px;
|
||||
Text {
|
||||
text: "Controllers: " + controller_count;
|
||||
font_size: 12px;
|
||||
color: #000000;
|
||||
}
|
||||
Text {
|
||||
text: "RAID Arrays: " + raid_count;
|
||||
font_size: 12px;
|
||||
color: #000000;
|
||||
}
|
||||
Text {
|
||||
text: "Drives: " + disk_count;
|
||||
font_size: 12px;
|
||||
color: #000000;
|
||||
}
|
||||
Rectangle { width: 100px; }
|
||||
Text {
|
||||
text: "Auto Refresh: " + (auto_refresh ? "On" : "Off");
|
||||
font_size: 12px;
|
||||
color: #000000;
|
||||
}
|
||||
Text {
|
||||
text: status_text;
|
||||
font_size: 12px;
|
||||
color: connected ? #008000 : #ff0000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||