Compare commits

..

9 Commits

49 changed files with 1590 additions and 12156 deletions
Vendored
BIN
View File
Binary file not shown.
-3
View File
@@ -1,3 +0,0 @@
[submodule "RAIDGuardX"]
path = RAIDGuardX
url = https://gitea.momentry.ddns.net/warren/RAIDGuardX.git
-234
View File
@@ -1,234 +0,0 @@
# 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
Generated
-6503
View File
File diff suppressed because it is too large Load Diff
+31 -6
View File
@@ -1,6 +1,31 @@
[workspace]
members = [
"raidguard_x_gui_server",
"raidguard_x_gui_client",
]
resolver = "2"
[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"
-286
View File
@@ -1,286 +0,0 @@
# 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*
-502
View File
@@ -1,502 +0,0 @@
# 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;
}
}
```
-305
View File
@@ -1,305 +0,0 @@
# 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(())
}
```
-256
View File
@@ -1,256 +0,0 @@
# 密碼系統分析
## 概述
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; // 驗證成功
}
```
-413
View File
@@ -1,413 +0,0 @@
# 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) {
// 取得控制器狀態
// 刷新頁面資料
}
}
```
-340
View File
@@ -1,340 +0,0 @@
# 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)*
-156
View File
@@ -1,156 +0,0 @@
# 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);
```
-327
View File
@@ -1,327 +0,0 @@
# 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 異步觸發
-281
View File
@@ -1,281 +0,0 @@
# 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 共享資料
-520
View File
@@ -1,520 +0,0 @@
# 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` - 模擬設定檔範例
- 編譯腳本更新
-361
View File
@@ -1,361 +0,0 @@
# 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 (如有需要)
Submodule RAIDGuardX deleted from f2379696bd
-268
View File
@@ -1,268 +0,0 @@
# 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.
+3
View File
@@ -0,0 +1,3 @@
fn main() {
slint_build::compile("ui/main_window.slint").unwrap();
}
-43
View File
@@ -1,43 +0,0 @@
#!/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"
Binary file not shown.

After

Width:  |  Height:  |  Size: 925 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

-4
View File
@@ -1,4 +0,0 @@
*.o
*.dSYM
test_mock_driver
test_all_pages
-38
View File
@@ -1,38 +0,0 @@
# 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
-205
View File
@@ -1,205 +0,0 @@
# 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.
-597
View File
@@ -1,597 +0,0 @@
/*
* 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;
}
-112
View File
@@ -1,112 +0,0 @@
#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
Binary file not shown.
@@ -1,261 +0,0 @@
/*
* 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;
}
@@ -1,20 +0,0 @@
<?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>
@@ -1,5 +0,0 @@
---
triple: 'arm64-apple-darwin'
binary-path: test_all_pages
relocations: []
...
@@ -1,83 +0,0 @@
/*
* 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;
}
+272
View File
@@ -0,0 +1,272 @@
//! 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()
}
}
+9
View File
@@ -0,0 +1,9 @@
//! 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;
+120
View File
@@ -0,0 +1,120 @@
//! 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(())
}
+145
View File
@@ -0,0 +1,145 @@
//! 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()
}
}
+275
View File
@@ -0,0 +1,275 @@
//! 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");
}
}
+69
View File
@@ -0,0 +1,69 @@
//! 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";
}
+7
View File
@@ -0,0 +1,7 @@
//! Client protocol module
pub mod client;
pub mod messages;
pub use client::*;
pub use messages::*;
-26
View File
@@ -1,26 +0,0 @@
//! 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),
}
}
+659
View File
@@ -0,0 +1,659 @@
// 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;
}
}
}
}
}