Ver código fonte

Added more UI for RAID

aroz 1 ano atrás
pai
commit
eac65dc647

+ 15 - 0
mod/disk/diskfs/diskfs.go

@@ -152,6 +152,21 @@ func UnmountDevice(devicePath string) error {
 	return nil
 }
 
+// Force Version of Unmount (dangerous)
+// Remember to use full path (e.g. /dev/md0) in the devicePath
+func ForceUnmountDevice(devicePath string) error {
+	// Construct the bash command to unmount the device
+	cmd := exec.Command("sudo", "bash", "-c", fmt.Sprintf("umount -l %s", devicePath))
+
+	// Run the command
+	err := cmd.Run()
+	if err != nil {
+		return fmt.Errorf("error unmounting device: %v", err)
+	}
+
+	return nil
+}
+
 // DANGER: Wipe the whole disk given the disk path
 func WipeDisk(diskPath string) error {
 	// Unmount the disk

+ 13 - 3
mod/disk/raid/handler.go

@@ -22,7 +22,7 @@ import (
 func (m *Manager) HandleMdadmFlushReload(w http.ResponseWriter, r *http.Request) {
 	err := m.FlushReload()
 	if err != nil {
-		utils.SendErrorResponse(w, "reload failed: "+err.Error())
+		utils.SendErrorResponse(w, "reload failed: "+strings.ReplaceAll(err.Error(), "\n", " "))
 		return
 	}
 	utils.SendOK(w)
@@ -114,13 +114,23 @@ func (m *Manager) HandleFormatRaidDevice(w http.ResponseWriter, r *http.Request)
 
 // List all the raid device in this system
 func (m *Manager) HandleListRaidDevices(w http.ResponseWriter, r *http.Request) {
-	rdev, err := m.GetRAIDDevicesFromProcMDStat()
+	rdevs, err := m.GetRAIDDevicesFromProcMDStat()
 	if err != nil {
 		utils.SendErrorResponse(w, err.Error())
 		return
 	}
 
-	js, _ := json.Marshal(rdev)
+	results := []*RAIDInfo{}
+	for _, rdev := range rdevs {
+		arrayInfo, err := m.GetRAIDInfo("/dev/" + rdev.Name)
+		if err != nil {
+			continue
+		}
+
+		results = append(results, arrayInfo)
+	}
+
+	js, _ := json.Marshal(results)
 	utils.SendJSONResponse(w, string(js))
 }
 

+ 5 - 0
mod/disk/raid/raiddetails.go

@@ -10,6 +10,7 @@ import (
 
 // RAIDInfo represents information about a RAID array.
 type RAIDInfo struct {
+	DevicePath     string
 	Version        string
 	CreationTime   time.Time
 	RaidLevel      string
@@ -38,6 +39,7 @@ type DeviceInfo struct {
 }
 
 // GetRAIDInfo retrieves information about a RAID array using the mdadm command.
+// arrayName must be in full path (e.g. /dev/md0)
 func (m *Manager) GetRAIDInfo(arrayName string) (*RAIDInfo, error) {
 	cmd := exec.Command("sudo", "mdadm", "--detail", arrayName)
 
@@ -47,6 +49,9 @@ func (m *Manager) GetRAIDInfo(arrayName string) (*RAIDInfo, error) {
 	}
 
 	info := parseRAIDInfo(string(output))
+
+	//Fill in the device path so other service can use it more easily
+	info.DevicePath = arrayName
 	return info, nil
 }
 

+ 14 - 0
web/SystemAO/disk/raid/img/drive-failed.svg

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="128px" height="128px" viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve">
+<rect x="12.458" y="81.333" fill="#727171" width="102.833" height="29.167"/>
+<polygon fill="#DCDDDD" points="96.374,40.167 30.208,40.167 12.542,81.333 115.374,81.333 "/>
+<path fill="#3E3A39" d="M113.125,105.811c0,1.729-1.357,3.127-3.034,3.127H17.535c-1.676,0-3.035-1.397-3.035-3.127V86.002
+	c0-1.729,1.359-3.127,3.035-3.127h92.556c1.677,0,3.034,1.398,3.034,3.127V105.811z"/>
+<circle fill="#00A0E9" cx="100.375" cy="95.916" r="3.708"/>
+<circle fill="#C30D23" cx="101.73" cy="48.736" r="20.897"/>
+<line fill="none" stroke="#FFFFFF" stroke-width="5" stroke-miterlimit="10" x1="110.299" y1="40.167" x2="93.162" y2="57.305"/>
+<line fill="none" stroke="#FFFFFF" stroke-width="5" stroke-miterlimit="10" x1="110.299" y1="57.305" x2="93.162" y2="40.167"/>
+</svg>

+ 14 - 0
web/SystemAO/disk/raid/img/drive-spare.svg

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="128px" height="128px" viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve">
+<rect x="12.458" y="81.333" fill="#727171" width="102.833" height="29.167"/>
+<polygon fill="#DCDDDD" points="96.374,40.167 30.208,40.167 12.542,81.333 115.374,81.333 "/>
+<path fill="#3E3A39" d="M113.125,105.811c0,1.729-1.357,3.127-3.034,3.127H17.535c-1.676,0-3.035-1.396-3.035-3.127V86.002
+	c0-1.729,1.359-3.127,3.035-3.127h92.556c1.677,0,3.034,1.398,3.034,3.127V105.811z"/>
+<circle fill="#00A0E9" cx="100.375" cy="95.916" r="3.708"/>
+<circle fill="#908FB3" cx="101.652" cy="48.736" r="20.897"/>
+<polygon fill="#FFFFFF" points="92.373,59.866 101.733,46.104 110.932,60.068 "/>
+<polygon fill="#FFFFFF" points="111.088,37.605 101.728,51.368 92.529,37.403 "/>
+</svg>

+ 14 - 0
web/SystemAO/disk/raid/img/drive-working.svg

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="128px" height="128px" viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve">
+<rect x="12.458" y="81.333" fill="#727171" width="102.833" height="29.167"/>
+<polygon fill="#DCDDDD" points="96.374,40.167 30.208,40.167 12.542,81.333 115.374,81.333 "/>
+<path fill="#3E3A39" d="M113.125,105.811c0,1.729-1.357,3.127-3.034,3.127H17.535c-1.676,0-3.035-1.398-3.035-3.127V86.002
+	c0-1.729,1.359-3.127,3.035-3.127h92.556c1.677,0,3.034,1.398,3.034,3.127V105.811z"/>
+<circle fill="#00A0E9" cx="100.375" cy="95.916" r="3.708"/>
+<circle fill="#33B418" cx="102.083" cy="51.5" r="20"/>
+<polyline fill="none" stroke="#FFFFFF" stroke-width="5" stroke-miterlimit="10" points="111.234,46.214 99.795,56.786 
+	92.932,50.868 "/>
+</svg>

+ 11 - 0
web/SystemAO/disk/raid/img/drive.svg

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="128px" height="128px" viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve">
+<rect x="12.458" y="81.333" fill="#727171" width="102.833" height="29.167"/>
+<polygon fill="#DCDDDD" points="96.374,40.167 30.208,40.167 12.542,81.333 115.374,81.333 "/>
+<path fill="#3E3A39" d="M113.125,105.811c0,1.728-1.358,3.127-3.034,3.127H17.535c-1.676,0-3.035-1.399-3.035-3.127V86.002
+	c0-1.728,1.359-3.127,3.035-3.127h92.556c1.676,0,3.034,1.399,3.034,3.127V105.811z"/>
+<circle fill="#00A0E9" cx="100.375" cy="95.916" r="3.708"/>
+</svg>

+ 180 - 152
web/SystemAO/disk/raid/index.html

@@ -11,8 +11,8 @@
         <script src="../disk/quota/script/Chart.min.js"></script>
         <style>
             .mdevice{
-                color: rgb(59, 59, 59);
-                font-size: 0.8em;
+                color: rgb(136, 136, 136);
+                font-weight: lighter;
             }
             .mdevuuid{
                 font-size: 0.6em !important;
@@ -66,6 +66,17 @@
                 margin-right: 0 !important;
             }
 
+            /* Statistics of Device Status */
+            .deviceOverview{
+                background-color: #f3f3f3 !important;
+                border-radius: 0.4em !important;
+            }
+
+            .deviceOverview .ui.statistics .statistic .label{
+                text-transform: none !important;
+                font-weight: 300;
+            }
+
             /* Danger zone css */
             .dangerzone{
                 border: 1px solid #dedede;
@@ -86,102 +97,20 @@
                 user-select:  none;
             }
 
-            /* Emulate the NAS disk interface */
-            .nasFrontPanel{
-                display: block;
-                border-radius: 0.4em;
-                background-color: white;
-                border: 1px solid rgb(214, 214, 214);
-                min-height: 150px;
-                padding: 1em;
-            }
-
-            .nasDisk{
-                width: 30px;
-                height:  120px;
-                background-color: white;
-                border-radius: 0.4em;
-                position: relative;
-                display: inline-block;
-                margin-right: 0.4em;
-                cursor: pointer;
-            }
-
-            .nasDisk:hover{
-                opacity: 0.7;
-            }
-
-            .nasDisk.inactive{
-                background: rgb(98,98,98);
-                background: linear-gradient(217deg, rgba(98,98,98,1) 12%, rgba(59,59,59,1) 77%); 
-            }
-
-            .nasDisk.active{
-                background: rgb(96,133,215);
-                background: linear-gradient(217deg, rgba(96,133,215,1) 12%, rgba(31,122,196,1) 77%);
-            }
-
-            .nasDisk.faulty{
-                background: rgb(215,96,96);
-                background: linear-gradient(217deg, rgba(215,96,96,1) 12%, rgba(168,51,43,1) 77%); 
-            }
-
-
-            .nasDisk .nasDiskLabel{
-                position: absolute;
-                transform-origin: 0 0;
-                color: white;
-                transform: rotate(-90deg);
-                width: 150px;
-                height: 40px;
-                margin-top: 120px;
-                padding: 0.6em;
-                padding-top: 0.6em;
-            }
-
-            .nasDisk .nasDiskNumberTag{
-                position: absolute;
-                top: 0em;
-                right: 0em;
-                height: 22px;
-                width: 100%;
-                background-color: rgb(44, 44, 44);
-                color: rgb(233, 233, 233);
-                text-align: center;
-                border-top-left-radius: 0.4em;
-                border-top-right-radius: 0.4em;
-            }
-
         </style>
     </head>
     <body>
-        <br>
         <div class="ui container">
+            <div class="ui basic segment">
+                <h3 class="ui header">
+                    <span id="RAIDhealthyState"></span>
+                    <div class="sub header"><span id="RAIDvolCount">0</span> RAID Storage Volume Detected</div>
+                </h3>
+            </div>
             <div class="ui stackable grid">
                 <div class="six wide column" style="border-right: 1px solid #e0e0e0;">
-                    <p>List of RAID Volumes</p>
                     <div id="raidVolList">
-                        <div class="ui segment raidVol">
-                            <h4 class="ui header">
-                                <img src="../../img/system/raid.svg">
-                                <div class="healthIcon"><i class="ui green check circle icon"></i></div>
-                                <div class="content">
-                                    <span>Volume 1</span> <span class="mdevice">(/dev/md0)</span>
-                                    <div class="sub header mdevuuid" style="margin-top: 0.4em;">
-                                        cbc11a2b:fbd42653:99c1340b:9c4962fb
-                                    </div>
-                                </div>
-                                <br><br>
-                                <div class="ui small fluid usedvol progress">
-                                    <div class="bar"></div>
-                                    <div class="label volinfo">
-                                        <div class="numeric">100 / 100PB</div>
-                                        <div class="percentage">1%</div>
-                                    </div>
-                                </div>
-                            </h4>
-                        </div>
-                        <div><i class="ui green check circle icon"></i> No RAID Volumes</div>
+                        <p style="text-align: center;"><i class="ui loading spinner icon"></i></p>
                     </div>
                     <div class="ui divider"></div>
                     <div style="width: 100%;" align="center">
@@ -191,26 +120,57 @@
                     </div>
                 </div>
                 <div class="ten wide column">
-                    <div id="volumeOverviews">
-                        <div class="ui basic segment">
-                            <h1 class="ui header">
-                                <span id="healthy"><i class="ui loading circle notch icon"></i> Loading</span>
-                                <div class="sub header"><span id="totalVolume">0</span> RAID volumes Loaded</div>
-                            </h1>
-                        </div>
-                        <div class="ui divider"></div>
-                        <div class="ui basic segment">
-                            <div id="diskEmu" class="nasFrontPanel">
-                                <div class="nasDisk">
-                                    <div class="nasDiskLabel">/dev/sda</div>
-                                    <div class="nasDiskNumberTag">0</div>
+                    <div id="volumeDetail">
+                        <div id="raidDiskOverview">
+                            <h3 class="ui header">
+                                <i class="ui green check circle icon"></i>
+                                <div class="content">
+                                    <span>/dev/md0</span>
+                                    <div class="sub header">10c0a9d9:763e326a:7d825b41:e1dc0536 | clean</div>
+                                </div>
+                            </h3>
+                            <div class="ui divider"></div>
+                            <div class="ui basic segment deviceOverview">
+                                <div class="ui tiny four statistics">
+                                    <div class="statistic">
+                                        <div class="value">
+                                        <img src="../disk/raid/img/drive.svg" class="ui inline image">
+                                        <span id="RAIDActiveDevices">0</span>
+                                        </div>
+                                        <div class="label">
+                                            Active Devices
+                                        </div>
+                                    </div>
+                                    <div class="statistic">
+                                        <div class="value">
+                                        <img src="../disk/raid/img/drive-working.svg" class="ui inline image">
+                                        <span id="RAIDWorkingDevices">0</span>
+                                        </div>
+                                        <div class="label">
+                                            Working Devices
+                                        </div>
+                                    </div>
+                                    <div class="statistic">
+                                        <div class="value">
+                                        <img src="../disk/raid/img/drive-failed.svg" class="ui inline image">
+                                        <span id="RAIDFailedDevices">0</span>
+                                        </div>
+                                        <div class="label">
+                                            Failed Devices
+                                        </div>
+                                    </div>
+                                    <div class="statistic">
+                                        <div class="value">
+                                        <img src="../disk/raid/img/drive-spare.svg" class="ui inline image">
+                                        <span id="RAIDSpareDevices">0</span>
+                                        </div>
+                                        <div class="label">
+                                            Spare Devices
+                                        </div>
+                                    </div>
                                 </div>
                             </div>
                         </div>
-                    </div>
-                    <div id="volumeDetail">
-
-                    
                         <div id="raidDiskList">
                             
                         </div>
@@ -252,61 +212,129 @@
                 </div>
             </div>
         <script>
-            var diskData = [{
-                "DevicePath" : "/dev/sda",
-                "State": ["active", "sync"]
-            },
-            {
-                "DevicePath" : "/dev/sdb",
-                "State": ["active", "sync"]
-            },
-            {
-                "DevicePath" : "/dev/sdc",
-                "State": ["faulty", "spare"]
-            },
-            {
-                "DevicePath" : "/dev/sdd",
-                "State": []
-            }];
 
-            $("#volumeDetail").hide();
+            //Quick function to check if the disk is healthy
+            function isHealthy(stateText) {
+                // Check if the stateText contains any unhealthy states
+                if (stateText.includes('faulty') || stateText.includes('resyncing') ||
+                    stateText.includes('recovering') || stateText.includes('degraded') ||
+                    stateText.includes('inactive')) {
+                    return false;  // Array is not healthy
+                } else {
+                    return true;  // Array is healthy
+                }
+            }
 
-            function GenerateEmulatedDisk(){
-                $("#diskEmu").html("");
+            //Get the RAID health state icon from state Text
+            function getStateIconFromStateText(stateText) {
+                if (stateText.includes('faulty')) {
+                    return 'ui red times circle icon';
+                } else if (stateText.includes('resyncing')) {
+                    return 'ui green loading sync icon';
+                } else if (stateText.includes('recovering')) {
+                    return 'ui yellow loading sync icon';
+                } else if (stateText.includes('degraded')) {
+                    return 'ui yellow exclamation triangle';
+                } else if (stateText.includes('inactive')) {
+                    return 'ui grey check sync icon';
+                } else if (stateText.includes('active')) {
+                    return 'ui green check circle icon';
+                } else if (stateText.includes('clean')) {
+                    return 'ui green check circle icon';
+                } else if (stateText.includes('spare')) {
+                    return 'ui blue check circle icon';
+                } else if (stateText.includes('reshape')) {
+                    return 'ui blue loading sync icon';
+                } else if (stateText.includes('frozen')) {
+                    return 'ui blue snowflake icon';
+                } else {
+                    return 'ui blue question circle icon';
+                }
+            }
 
-                //Generate the list of disk base on diskData
-                let diskCounter = 0;
-                diskData.forEach(function(diskInfo){
-                    let diskStateClass = "inactive";
-                    if (diskInfo.State.length > 0){
-                        for (var i = 0; i < diskInfo.State.length; i++){
-                            let thisState = diskInfo.State[i];
-                            if (thisState == "sync" || thisState == "active"){
-                                diskStateClass = "active";
-                            }else if (thisState == "faulty"){
-                                diskStateClass = "faulty"
-                            }
+            function loadRAIDVolDetail(deviceName){
+                function capitalize(string) {
+                    return string.charAt(0).toUpperCase() + string.slice(1);
+                }
 
-                            //Add more state here if required
-                        }
+                //Get the information of the device
+                $("#raidDiskList").html("");
+                $.get("../../system/disk/raid/detail?devName=" + deviceName, function(data){
+                    if (data.error != undefined){
+                        $("#raidDiskList").html(`<h3 class="ui header">
+                            <i class="ui red circle times icon"></i> 
+                            <div class="content" style="font-weight: 300;">
+                                Unable to load RAID volume information 
+                                <div class="sub header">${capitalize(data.error)}</div>
+                            </div>
+                            
+                        </h3>`);
+                        return;
                     }
 
-                    console.log(diskStateClass);
-                    $("#diskEmu").append(`
-                        <div class="nasDisk ${diskStateClass}">
-                            <div class="nasDiskLabel">${diskInfo.DevicePath}</div>
-                            <div class="nasDiskNumberTag">${diskCounter}</div>
-                        </div>
-                    `);
-                   
+                    //Render the disk list
+                    $("#raidDiskList").html("");
+
+                    //Update the active disks info
+                    $("#RAIDActiveDevices").text(data.ActiveDevices);
+                    $("#RAIDWorkingDevices").text(data.WorkingDevices);
+                    $("#RAIDFailedDevices").text(data.FailedDevices);
+                    $("#RAIDSpareDevices").text(data.SpareDevices);
 
 
-                    diskCounter++;
                 });
             }
 
-            GenerateEmulatedDisk();
+            //Initialize the RAID volume list on this system
+            function initRAIDVolList(){
+                $("#raidVolList").html(`<p style="text-align: center;"><i class="ui loading spinner icon"></i></p>`);
+                $.get("../../system/disk/raid/list", function(data){
+                    if (data.error != undefined){
+                        $("#raidVolList").html(`<p style="text-align: center;"><i class="ui red circle remove icon"></i> ${data.error}</p>`);
+                        return;
+                    }
+
+                    $("#raidVolList").html("");
+                    let containUnhealthyDisk = false;
+                    for (var i = 0; i < data.length; i++){
+                        let thisDiskInfo = data[i];
+                        $("#raidVolList").append(`<div class="ui segment raidVol">
+                            <h4 class="ui header">
+                                <img src="../../img/system/raid.svg">
+                                <div class="healthIcon"><i class="${getStateIconFromStateText(thisDiskInfo.State)}"></i></div>
+                                <div class="content" style="margin-left: 0.6em;">
+                                    <span>${thisDiskInfo.DevicePath}</span> <span class="mdevice">(${thisDiskInfo.RaidLevel})</span>
+                                    <div class="sub header mdevuuid" style="margin-top: 0.4em;">
+                                        ${thisDiskInfo.UUID}
+                                    </div>
+                                </div>
+                            </h4>
+                        </div>`);
+                        if (!isHealthy(thisDiskInfo.State)){
+                            containUnhealthyDisk = true;
+                        }
+                        console.log(thisDiskInfo);
+                    }
+
+                    if (data.length == 0){
+                        $("#raidVolList").html(`<div><i class="ui green check circle icon"></i> No RAID Volumes</div>`);
+                    }else{
+                        //Load the first RAID vol info
+                        loadRAIDVolDetail(data[0].DevicePath);
+                    }
+                    $("#RAIDvolCount").text(data.length);
+
+                    //Update the overall health text
+                    if (containUnhealthyDisk){
+                        $("#RAIDhealthyState").html("<i class='ui yellow exclamation triangle icon'></i> Attention Needed!");
+                    }else{
+                        $("#RAIDhealthyState").html("<i class='ui green circle check icon'></i> Healthy");
+                    }
+
+                });
+            }
+            initRAIDVolList();
+            
         </script>
-   
     </body>
 </html>

+ 2 - 2
web/SystemAO/disk/smart/smart.html

@@ -27,7 +27,7 @@
             <br>
             <div class="ui divider"></div>
         </div>
-        <div class="ui modal">
+        <div class="ui modal smartdetails">
           <i class="close icon"></i>
           <div class="header">
             SMART Information (<span id="modal_model"></span>)
@@ -97,7 +97,7 @@
             */
             $(".loadedModal").modal('destroy');
             $(".loadedModal").remove();
-            $(".ui.modal").addClass("loadedModal");
+            $(".smartdetails").addClass("loadedModal");
 
             
             function append(id, name, status, capacity){

+ 10 - 2
web/SystemAO/disk/space/diskspace.html

@@ -38,7 +38,6 @@
                             }
                             if (thisDev.Used == 0 && thisDev.Volume == 0){
                                 color = "black";
-                                console.log("Oops");
                                 usedPercentage = 100;
                             }
                             var tmpfs = "";
@@ -47,9 +46,18 @@
                                 tmpfs = "tmpfs";
                                 style = "display:none;";
                             }
+
+                            //Check if it is RAID partitions
+                            let deviceName = thisDev.Device.split("/").pop();
+                            let diskIcon = "disk.svg";
+                            if (deviceName.substr(0, 2) == "md"){
+                                //It is RAID device
+                                color = "teal";
+                                diskIcon = "raid.svg";
+                            }
                             $("#diskInfo").append(`<div class="event ${tmpfs}" style="${style}">
                                 <div class="label">
-                                    <img src="../disk/space/img/disk.svg">
+                                    <img src="../disk/space/img/${diskIcon}">
                                 </div>
                                 <div class="content">
                                     <div class="summary">

BIN
web/SystemAO/disk/space/img/raid.png


+ 18 - 0
web/SystemAO/disk/space/img/raid.svg

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="128px"
+	 height="128px" viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve">
+<g id="圖層_1">
+</g>
+<g id="圖層_2">
+	<rect x="15.623" y="79.039" fill="#3E3A39" width="98.418" height="27.934"/>
+	<ellipse fill="#727171" cx="64.832" cy="79.038" rx="49.209" ry="12.69"/>
+	<rect x="15.623" y="51.106" fill="#727171" width="98.418" height="27.933"/>
+	<ellipse fill="#DCDDDD" cx="64.832" cy="51.105" rx="49.209" ry="12.689"/>
+	<rect x="15.623" y="23.128" fill="#DCDDDD" width="98.418" height="27.978"/>
+	<ellipse fill="#3E3A39" cx="64.832" cy="105.695" rx="49.209" ry="12.689"/>
+	<ellipse fill="#DCDDDD" stroke="#F7F8F8" stroke-miterlimit="10" cx="64.832" cy="24.427" rx="49.209" ry="12.689"/>
+	<ellipse fill="#00A0E9" cx="103.481" cy="99.728" rx="3.186" ry="5.968"/>
+</g>
+</svg>