Selaa lähdekoodia

Added vol display to RAID

TC 2 viikkoa sitten
vanhempi
commit
6c9a34f9c8
5 muutettua tiedostoa jossa 258 lisäystä ja 29 poistoa
  1. 3 0
      mod/diskinfo/diskutil.go
  2. 4 0
      raid.go
  3. 27 0
      web/components/disks.html
  4. 222 29
      web/components/raid.html
  5. 2 0
      web/js/locale.js

+ 3 - 0
mod/diskinfo/diskutil.go

@@ -5,6 +5,8 @@ import (
 	"path/filepath"
 	"strings"
 
+	"log"
+
 	"imuslab.com/bokofs/bokofsd/mod/diskinfo/blkid"
 	"imuslab.com/bokofs/bokofsd/mod/diskinfo/df"
 	"imuslab.com/bokofs/bokofsd/mod/diskinfo/fdisk"
@@ -44,6 +46,7 @@ func DevicePathIsValidDisk(path string) bool {
 
 	allBlockDevices, err := lsblk.GetLSBLKOutput()
 	if err != nil {
+		log.Println("Error getting block devices:", err)
 		return false
 	}
 

+ 4 - 0
raid.go

@@ -24,6 +24,10 @@ func HandleRAIDCalls() http.Handler {
 			// Handle loading the detail of a given RAID array, require "dev=md0" as a query parameter
 			raidManager.HandleLoadArrayDetail(w, r)
 			return
+		case "overview":
+			// Render the RAID overview page
+			raidManager.HandleRenderOverview(w, r)
+			return
 		case "sync":
 			// Get the RAID sync state, require "dev=md0" as a query parameter
 			raidManager.HandleGetRAIDSyncState(w, r)

+ 27 - 0
web/components/disks.html

@@ -19,6 +19,9 @@
     </div>
 </div>  
 <script>
+    let hostDiskInfo = {
+        "disks": []
+    };
     function humanFileSize(size) {
         var i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024));
         return +((size / Math.pow(1024, i)).toFixed(1)) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];
@@ -36,6 +39,7 @@
                 var disks = data;
                 var diskList = $("#disk-list");
                 diskList.empty();
+                hostDiskInfo.disks = disks;
                 for (var i = 0; i < disks.length; i++) {
                     let disk = disks[i];
                     let partitionDOM = "";
@@ -137,4 +141,27 @@
         });
     }
     loadDiskInfo();
+
+    /* extern functions */
+
+    // Get disk info by device path. Returns the disk or partition object.
+    // return null if not found.
+    function getDiskInfoDevicePath(devpath) {
+        if (devpath.startsWith("/dev/")) {
+            devpath = devpath.substring(5);
+        }
+        hostDiskInfo.disks.forEach(function(disk) {
+            console.log(disk.name, devpath);
+            if (disk.name == devpath) {
+                return disk;
+            }
+            disk.partitions.forEach(function(partition) {
+                if (partition.path == devpath) {
+                    return partition;
+                }
+            });
+        });
+
+        return null;
+    }
 </script>

+ 222 - 29
web/components/raid.html

@@ -3,21 +3,33 @@
         background: var(--ts-positive-400) !important; 
         border: 0px solid transparent !important;
     }
+
+    .raid-details{
+        position: relative;
+    }
+
+    .raid-array-opr-btns{
+        position: absolute !important;
+        top: 1em;
+        right: 1em;
+    }
+
+    .raid-menu-badge{
+        margin-top: -3px !important;
+        margin-left: 0.15em !important;
+    }
 </style>
 <div class="ts-content">
     <div class="ts-container is-padded">
         <div class="ts-grid mobile:is-stacked">
             <div class="column is-6-wide">
                 <div id="raid_array_list" class="ts-menu is-start-icon is-separated">
-                    <a class="item">
-                        <span class="ts-icon is-user-icon"></span> 使用者
-                    </a>
-                    <a class="item is-active">
-                        <span class="ts-icon is-house-icon"></span> 首頁
-                    </a>
-                    <a class="item">
-                        <span class="ts-icon is-newspaper-icon"></span> 新聞
-                    </a>
+                    <div class="ts-blankslate" style="pointer-events: none; user-select: none; opacity: 0.7;">
+                        <div class="description">
+                            <span class="ts-icon is-circle-notch-icon is-spinning"></span>
+                            <span class="has-start-spaced-small">Loading...</span>
+                        </div>
+                    </div>
                 </div>
                 <div class="ts-divider has-top-spaced-small"></div>
                 <div class="ts-content is-center-aligned">
@@ -60,6 +72,7 @@
                         let raidDetails = renderRAIDPoolDetail(raid, index);
                         $("#raid_array_list").append(raidDetails[0]);
                         $('#raid_details').append(raidDetails[1]);
+                        getRAIDSpaceInfoForDev(raid.DevicePath);
                     });
 
                     if (data.length == 0){
@@ -89,7 +102,19 @@
     }
     initRAIDDeviceList();
 
-    function updateRAIDArrayStatus(devname){
+    function refreshRAIDArrayStatus(devname){
+        //Hide the raid details
+        $(`.raid-details[mdx=${devname}]`).hide();
+        updateRAIDArrayStatus(devname, function(data){
+            if (data.error == undefined){
+               msgbox(i18nc("raid_device_updated_succ"));
+            }
+            $(`.raid-details[mdx=${devname}]`).show();
+        });
+
+    }
+
+    function updateRAIDArrayStatus(devname, callback=undefined){
         if (devname.startsWith('/dev/')) {
             devname = devname.slice(5);
         }
@@ -102,9 +127,14 @@
                     // Handle error response
                     console.error('Error fetching RAID status:', data.error);
                     msgbox("Error: " + data.error);
+                    if (callback){
+                        callback(data);
+                    }
                     return
                 }
 
+                // Update the RAID array status
+                // Find the corresponding menu item and details tab
                 let menuItem = $(`.raid-array[mdx="${devname}"]`);
                 let raidDetails = $(`.raid-details[mdx="${devname}"]`);
                 let index = menuItem.attr("idx");
@@ -117,6 +147,9 @@
                 raidDetails.replaceWith(domEles[1]);
                 showRAIDDetails(currentShownDetailIndex);
                 syncProgressTicker();
+                if (callback){
+                    callback(data);
+                }
             },
             error: function(xhr, status, error) {
                 console.error('Error updating RAID status:', error);
@@ -124,41 +157,95 @@
         });
     }
 
+    function deleteRAIDArray(devname){
+        if (devname.startsWith('/dev/')) {
+            devname = devname.slice(5);
+        }
+
+        /*
+        //TODO: API still not ready
+        $.cjax({
+            url: './api/raid/delete',
+            method: 'POST',
+            data: { dev: devname},
+            success: function(data) {
+                if (data.error != undefined){
+                    // Handle error response
+                    console.error('Error deleting RAID device:', data.error);
+                    msgbox("Error: " + data.error);
+                }else{
+                    // Successfully deleted the device
+                    console.log('RAID device deleted successfully:', data);
+                    msgbox(i18nc("raid_device_deleted_succ"));
+                    setTimeout(function() {
+                        // Refresh the RAID device list after a short delay
+                        initRAIDDeviceList();
+                    }, 300);
+                }
+            },
+        });
+        */
+    }
+
+    // Utility function to convert bytes to human-readable format
+    function bytesToHumanReadable(bytes) {
+        const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
+        if (bytes === 0) return '0 B';
+        const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
+        return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + sizes[i];
+    }
+
+
+    function getRAIDSpaceInfoForDev(devname){
+        if (devname.startsWith('/dev/')) {
+            devname = devname.slice(5);
+        }
+        let gaugeElement = $(`.raid-details[mdx=${devname}]`).find(".raid-usage-info");
+        let updateElement = $(`.raid-details[mdx=${devname}]`).find(".raid-total-used-space");
+        $.get("./api/raid/overview", function(data){
+            if (data.error != undefined){
+                // Handle error response
+                return;
+            }
+            if (data && Array.isArray(data)) {
+                let raidInfo = data.find(raid => raid.Name === devname);
+                if (raidInfo) {
+                    let usedPercentage = (raidInfo.UsedSize / raidInfo.TotalSize) * 100;
+                    gaugeElement.find('.bar').css('--value', usedPercentage.toFixed(1));
+                    gaugeElement.find('.bar .text').text(`${usedPercentage.toFixed(1)}%`);
+                    updateElement.html(bytesToHumanReadable(raidInfo.UsedSize));
+                }
+            }
+        })
+    }
+
     // Function to render RAID pool details
     // This function creates the HTML structure for each RAID pool
     // return the DOM element for the side menu and detail tab
     function renderRAIDPoolDetail(raid, index){
-        // Utility function to convert bytes to human-readable format
-        function bytesToHumanReadable(bytes) {
-            const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
-            if (bytes === 0) return '0 B';
-            const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
-            return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + sizes[i];
-        }
-
          // Add a new menu item for each RAID array
          let mdX = raid.DevicePath.split('/').pop();
          let isSyncing = false;
          let isResyncPending = false;
          let icon = '';
             if (raid.State.includes('clean') && !raid.State.includes('sync')) {
-                icon = '<span class="ts-icon is-check-icon" style="color: var(--ts-positive-500);"></span>';
+                icon = '<span class="ts-icon is-check-icon" style="color: var(--ts-positive-500); font-size: 2em;"></span>';
             } else if (raid.State.includes('sync')) {
                 isSyncing = true;
                 if (raid.State.includes('resyncing') && raid.State.includes('PENDING')) {
                     //Syncing is pending
                     isResyncPending = true;
-                    icon = '<span class="ts-icon is-rotate-icon" style="color: var(--ts-positive-500);"></span>';
+                    icon = '<span class="ts-icon is-rotate-icon" style="color: var(--ts-positive-500); font-size: 2em;"></span>';
                 }else{
-                    icon = '<span class="ts-icon is-spinning is-rotate-icon" style="color: var(--ts-positive-500);"></span>';
+                    icon = '<span class="ts-icon is-spinning is-rotate-icon" style="color: var(--ts-positive-500); font-size: 2em;"></span>';
                 }
                 
             } else if (raid.State.includes('degraded')) {
-                icon = '<span class="ts-icon is-triangle-exclamation-icon" style="color: var(--ts-warning-600);"></span>';
+                icon = '<span class="ts-icon is-triangle-exclamation-icon" style="color: var(--ts-warning-600); font-size: 2em;"></span>';
             } else if (raid.State.includes('fail')) {
-                icon = '<span class="ts-icon is-circle-xmark-icon" style="color: var(--ts-negative-500);"></span>';
+                icon = '<span class="ts-icon is-circle-xmark-icon" style="color: var(--ts-negative-500); font-size: 2em;"></span>';
             } else {
-                icon = '<span class="ts-icon is-question-icon" style="color: var(--ts-gray-500);"></span>';
+                icon = '<span class="ts-icon is-question-icon" style="color: var(--ts-gray-500); font-size: 2em;"></span>';
             }
 
             // Add a new menu item for each RAID array
@@ -167,7 +254,8 @@
                     ${icon}
                     <div class="ts-content is-dense">
                         <div>
-                            <span class="ts-text is-heavy">${raid.DevicePath}</span> | ${raid.RaidLevel.toUpperCase()}
+                            <span class="ts-text is-heavy">${raid.DevicePath}</span>
+                            <span class="ts-badge is-secondary raid-menu-badge">${raid.RaidLevel.toUpperCase()}</span>
                         </div>
                         <div class="ts-text is-tiny has-top-spaced-small">
                             ${raid.Name}
@@ -183,7 +271,7 @@
                         <div class="ts-content is-padded">
                             <div class="ts-header is-start-icon">
                                 ${icon}
-                                ${raid.DevicePath} | ${raid.RaidLevel.toUpperCase()}
+                                ${raid.DevicePath} <span class="ts-badge is-start-spaced">${raid.RaidLevel.toUpperCase()}</span> 
                             </div>
                             <div class="ts-text is-description">
                                 ${raid.UUID}<br>
@@ -204,6 +292,8 @@
                                     // 建立時間
                                 </span>: <span>${new Date(raid.CreationTime).toLocaleString('en-US', { timeZone: 'UTC' })}</span><br>
                             </div>
+                            
+                            <!-- Disk States Summary -->
                             <table class="ts-table is-single-line has-top-spaced-large">
                                 <thead>
                                     <tr>
@@ -242,13 +332,55 @@
                                     </tr>
                                 </tbody>
                             </table>
+
+                            <!-- Usage Counters -->
+                            <div class="ts-grid is-evenly-divided has-top-spaced-large">
+                                <div class="column">
+                                    <div class="ts-wrap is-middle-aligned">
+                                        <div class="ts-gauge is-small is-circular raid-usage-info">
+                                            <div class="bar" style="--value: 0">
+                                                <div class="text">0%</div>
+                                            </div>
+                                        </div>
+                                        <div>
+                                            <div class="ts-text is-bold" i18n> Used Space
+                                                // 已使用空間
+                                            </div>
+                                           <span class="raid-total-used-space">???</span> / ${bytesToHumanReadable(raid.ArraySize * 1024)}
+                                        </div>
+                                    </div>
+                                </div>
+                                <div class="column">
+                                    <div class="ts-wrap is-middle-aligned">
+                                        <div class="ts-gauge is-small is-circular">
+                                            <div class="bar" style="--value: 100">
+                                                <div class="text">---</div>
+                                            </div>
+                                        </div>
+                                        <div>
+                                            <div class="ts-text is-bold">傳輸</div>
+                                            0 B 已使用
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+
                         </div>
                     </div>
-                    <div class="ts-box is-padded has-top-spaced-small">
+                    <div class="has-top-spaced-small">
+                        ${getRAIDChildDiskElement(raid.DeviceInfo)}
+                    </div>
+                    <!-- Operations -->
+                    <div class="raid-array-opr-btns">
                         <div class="ts-content">
-                            
+                            <button class="ts-button is-circular is-icon" onclick="refreshRAIDArrayStatus('${mdX}');">
+                                <span class="ts-icon is-arrows-rotate-icon"></span>
+                            </button>
+                            <button class="ts-button is-circular is-icon is-negative" onclick="deleteRAIDArray('${mdX}');">
+                                <span class="ts-icon is-trash-icon"></span>
+                            </button>
                         </div>
-                    </div>
+                    </div>  
                 </div>
             `;
         return [menuItem, raidDetails];
@@ -300,6 +432,67 @@
         </button>`
     }
 
+    // DOM elements for child disks
+    function getRAIDChildDiskElement(raidDeviceInfo){
+        if (raidDeviceInfo.length == 0 || raidDeviceInfo == null){
+            return `<div class="ts-blankslate" style="pointer-events: none; user-select: none; opacity: 0.7;">
+                <div class="description" i18n>No assigned disks
+                    // 沒有分配的磁碟
+                </div>
+            </div>`;
+        }
+
+        //Render each disk
+        let result = '';
+        for (let i = 0; i < raidDeviceInfo.length; i++) {
+            let disk = raidDeviceInfo[i];
+            if (disk.RaidDevice == -1){
+                continue;
+            }
+
+            let elementUUID = "raid_child_" + disk.DevicePath.replace(/\//g, "_");
+            let diskSdx = disk.DevicePath.split('/').pop();
+            let thisDiskInfo = getDiskInfoDevicePath(diskSdx); //Try to load disk info from cache
+            if (thisDiskInfo == null){
+                //Make a request to get the disk info
+                let thisDiskInfo = null;
+                $.ajax({
+                    url: `./api/info/disk/${diskSdx}`,
+                    type: 'GET',
+                    dataType: 'json',
+                    success: function(data) {
+                        if (data.error != undefined){
+                            return;
+                        }
+                        thisDiskInfo = data;
+                        $(`.raid-child-disk[diskid='${elementUUID}']`).find(".raid-disk-name").text(thisDiskInfo.model);
+                    },
+                    error: function(xhr, status, error) {
+                        console.error('Error fetching disk info:', error);
+                    }
+                });
+            }
+            result += `
+            <div class="ts-box is-padded has-bottom-spaced-small raid-child-disk" diskid="${elementUUID}">
+                <div class="ts-content">
+                    <div>
+                        <span class="ts-badge is-secondary has-end-spaced-small" style="margin-top: -0.3em;">${disk.DevicePath}</span>
+                        <span class="ts-text is-heavy raid-disk-name">Raid Device ${disk.RaidDevice}</span>
+                    </div>
+                    <div class="ts-text is-tiny has-top-spaced-small">
+                    <div class="has-start-spaced-small">
+                        <span i18n> State
+                        // 狀態
+                        </span>: ${disk.State.join(', ')}
+                    </div>
+                    </div>
+                </div>
+            </div>
+            `;
+        }
+        return result;
+    }
+
     // Function to activate a finished RAID sync
     // Will set the RAID device to -readwrite state
     function activateSyncPendingDisk(devname){

+ 2 - 0
web/js/locale.js

@@ -59,9 +59,11 @@ let translatedMessages = {
     'en': {
         'disk_info_refreshed': 'Disk information reloaded',
         "raid_resync_started_succ": 'RAID resync started successfully',
+        "raid_device_updated_succ": 'RAID device status reloaded',
     },
     'zh': {
         'disk_info_refreshed': '磁碟資訊已重新載入',
         "raid_resync_started_succ": 'RAID 重建已成功啟動',
+        "raid_device_updated_succ": 'RAID 裝置狀態已重新載入',
     }
 };