Просмотр исходного кода

Added RAID delete and create feature

TC 2 дней назад
Родитель
Сommit
32d22a92c0
6 измененных файлов с 295 добавлено и 49 удалено
  1. 0 1
      mod/disktool/raid/handler.go
  2. 14 1
      raid.go
  3. 5 0
      web/components/disks.html
  4. 154 41
      web/components/raid.html
  5. 114 6
      web/components/raid_new.html
  6. 8 0
      web/js/locale.js

+ 0 - 1
mod/disktool/raid/handler.go

@@ -476,7 +476,6 @@ func (m *Manager) HandleRaidDevicesAssemble(w http.ResponseWriter, r *http.Reque
 
 // Remove a given raid device with its name, USE WITH CAUTION
 func (m *Manager) HandleRemoveRaideDevice(w http.ResponseWriter, r *http.Request) {
-	//TODO: Add protection and switch to POST
 	targetDevice, err := utils.PostPara(r, "raidDev")
 	if err != nil {
 		utils.SendErrorResponse(w, "target device not given")

+ 14 - 1
raid.go

@@ -28,6 +28,11 @@ 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 "create":
+			// Create a new RAID device, devName (Optional, e.g. md0), raidName (e.g. myraid), level (e.g. 0),
+			// raidDev(e.g. sda,sdb), spareDev(e.g. sdc), zerosuperblock(bool)
+			raidManager.HandleCreateRAIDDevice(w, r)
+			return
 		case "overview":
 			// Render the RAID overview page
 			raidManager.HandleRenderOverview(w, r)
@@ -44,8 +49,16 @@ func HandleRAIDCalls() http.Handler {
 			// Reassemble all RAID devices
 			raidManager.HandleForceAssembleReload(w, r)
 			return
-
+		case "delete":
+			// Delete a RAID device, require "raidDev=md0" as a query parameter
+			raidManager.HandleRemoveRaideDevice(w, r)
+			return
+		case "add":
+			// Add a new disk to the RAID device, require "dev=md0" as a query parameter
+			raidManager.HandleAddDiskToRAIDVol(w, r)
+			return
 		case "test":
+			//DEBUG Code
 			devname, err := utils.GetPara(r, "dev")
 			if err != nil {
 				http.Error(w, err.Error(), http.StatusBadRequest)

+ 5 - 0
web/components/disks.html

@@ -34,6 +34,11 @@
     });
 
     function loadDiskInfo(){
+        $("#disk-list").html(`
+            <div class="ts-blankslate">
+                <div class="header"><span class="ts-icon is-spinning is-spinner-icon"></span></div>
+            </div>
+        `);
         $.get("./api/info/list", function(data){
             if (data) {
                 var disks = data;

+ 154 - 41
web/components/raid.html

@@ -49,13 +49,13 @@
                 </div>
                 <div class="ts-divider has-top-spaced-small"></div>
                 <div class="ts-content is-center-aligned">
-                    <button class="ts-button is-start-icon is-positive is-circular">
+                    <button onclick="showCreateNewRAIDArray();" class="ts-button is-start-icon is-positive is-circular">
                         <span class="ts-icon is-circle-plus-icon" style="color: var(--ts-positive-500);"></span>
                         <span i18n>Create RAID
                         // 新增陣列
                         </span>
                     </button>
-                    <button onclick="showForceAssembleWarning();" class="ts-button is-start-icon is-positive is-circular">
+                    <button onclick="showForceAssembleWarning();" class="ts-button is-start-icon has-start-spaced-small is-positive is-circular">
                         <span class="ts-icon is-rotate-icon" style="color: var(--ts-primary-500);"></span>
                         <span i18n>Assemble
                         // 重組陣列
@@ -70,9 +70,8 @@
             </div>
         </div>
     </div>
-    <style>
-        
-    </style>
+
+    <!-- RAID Assemble Warning Dialog -->
     <dialog id="raid_assemble_warning" class="ts-modal">
         <div class="content">
             <div class="ts-content">
@@ -101,14 +100,48 @@
             </div>
         </div>
     </dialog>
-    <dialog id="raid_new" class="ts-modal is-big mobile:is-fullscreen" open>
 
+    <!-- RAID Device Remove Warning -->
+    <dialog id="raid_remove_warning" class="ts-modal">
+        <div class="content">
+            <div class="ts-content">
+                <div class="ts-header" i18n>Confirm delete RAID device?
+                    // 確認刪除 RAID 裝置?
+                </div>
+            </div>
+            <div class="ts-divider"></div>
+            <div class="ts-content">
+                <div class="ts-text is-description">
+                    <span i18n> This will delete the RAID device and all data on it. 
+                        // 這將刪除 RAID 裝置及其上的所有資料。
+                    </span>
+                </div>
+            </div>
+            <div class="ts-divider"></div>
+            <div class="ts-content is-tertiary">
+                <div class="ts-wrap is-end-aligned">
+                    <button class="ts-button is-negative" onclick="deleteRAIDArray();" i18n>Confirm
+                        // 確認
+                    </button>
+                    <button class="ts-button" onclick="cancelRAIDDelete();" i18n>Cancel
+                        // 取消
+                    </button>
+                </div>
+            </div>
+        </div>
     </dialog>
+
+    <!-- RAID Creation Dialog -->
+    <dialog id="raid_new" class="ts-modal is-big mobile:is-fullscreen"></dialog>
+
 </div>
 <script>
+    // Load the RAID creation dialog
     $("#raid_new").load("./components/raid_new.html", function(){
         
     });
+
+    // Load existing RAID devices
     function initRAIDDeviceList(){
         $.ajax({
             url: './api/raid/list',
@@ -116,6 +149,7 @@
             dataType: 'json',
             success: function(data) {
                 $('#raid_array_list').html("");
+                $("#raid_details").html("");
                 if (data.error != undefined){
                     // Handle error response
                     console.error('Error fetching RAID devices:', data.error);
@@ -211,36 +245,6 @@
         });
     }
 
-    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'];
@@ -417,13 +421,15 @@
                     <div class="has-top-spaced-small">
                         <div class="ts-box">
                             <div class="ts-content">
+                                <!-- 
                                 <button class="ts-button is-start-icon is-positive is-circular" onclick="" >
                                     <span class="ts-icon is-rotate-icon" style="color: var(--ts-primary-500);"></span>
                                     <span i18n> Assemble
                                         // 重組陣列
                                     </span>
                                 </button>
-                                <button class="ts-button is-circular is-start-icon is-negative"> 
+                                -->
+                                <button onclick="showDeleteRAIDWarning('${raid.DevicePath}');" class="ts-button is-circular is-start-icon is-negative"> 
                                     <span class="ts-icon is-trash-icon"></span>
                                     <span i18n> Delete RAID
                                         // 刪除陣列
@@ -531,8 +537,7 @@
                     }
                 });
             }
-            result += `
-            <div class="ts-box is-padded has-bottom-spaced-small raid-child-disk" diskid="${elementUUID}">
+            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>
@@ -546,8 +551,7 @@
                     </div>
                     </div>
                 </div>
-            </div>
-            `;
+            </div>`;
         }
         return result;
     }
@@ -660,5 +664,114 @@
         $('#raid_assemble_warning')[0].close();
         AssembleAllRAID();
     }
+
+    /* Delete RAID */
+    function showDeleteRAIDWarning(devname){
+        $('#raid_remove_warning')[0].showModal();
+        $('#raid_remove_warning').attr("devname", devname);
+    }
+
+    function deleteRAIDArray(devname=undefined){
+        if (devname == undefined){
+            // Get the device name from the dialog
+            devname = $('#raid_remove_warning').attr("devname");
+        }
+        if (!devname.startsWith('/dev/')) {
+            devname = '/dev/' + devname;
+        }
+        $('#raid_remove_warning')[0].close();
+        $.cjax({
+            url: './api/raid/delete',
+            method: 'POST',
+            data: { "raidDev": 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);
+                }
+            },
+        });
+        
+    }
+
+    function cancelRAIDDelete(){
+        $('#raid_remove_warning')[0].close();
+    }
+
+    /* Create RAID */
+    function showCreateNewRAIDArray(){
+        $('#raid_new')[0].showModal();
+        resetNewRAIDUserInputs();
+    }
+
+    function closeCreateNewRAIDArray(){
+        $('#raid_new')[0].close();
+    }
+
+    /*
+        Require raid_new.html, return e.g.
+        {
+            disks: selectedDisks,
+            type: selectedRAIDType,
+            name: raidName
+        }
+    */
+    function createNewRAIDArray(formatSuperBlock=true){
+        let newArrayconfig = getNewRAIDArraySelection();
+        let diskNames = [];
+        let arrayName = newArrayconfig.name;
+        let raidType = newArrayconfig.type;
+        newArrayconfig.disks.forEach((disk) => {
+            diskNames.push("/dev/" + disk.name);
+        });
+        console.log(diskNames, raidType, arrayName, formatSuperBlock);
+        
+        //Make a request to create the new RAID array
+        $.cjax({
+            url: './api/raid/create',
+            method: 'POST',
+            data: { 
+                "raidName": arrayName,
+                "level": raidType,
+                "raidDev": JSON.stringify(diskNames),
+                "spareDev":JSON.stringify([]),
+                "zerosuperblock": formatSuperBlock
+            },
+            success: function(data) {
+                if (data.error != undefined){
+                    // Handle error response
+                    console.error('Error creating RAID device:', data.error);
+                    msgbox("Error: " + data.error);
+                }else{
+                    // Successfully created the device
+                    console.log('RAID device created successfully:', data);
+                    msgbox(i18nc("raid_device_created_succ"));
+                    setTimeout(function() {
+                        // Refresh the RAID device list after a short delay
+                        initRAIDDeviceList();
+                        $('#raid_new')[0].close();
+
+                        // Reset the RAID creation dialog inputs
+                        resetNewRAIDUserInputs();
+                        hideConfirmNewRaidDialog();
+                        
+                    }, 300);
+                }
+            },
+        });
+    }
+
+    function cancelCreateNewRAIDArray(){
+        $('#raid_new')[0].close();
+    }
 </script>
 

+ 114 - 6
web/components/raid_new.html

@@ -150,7 +150,13 @@
                     <button class="item raid_type" value="raid5">RAID 5 </button>
                     <button class="item raid_type" value="raid0">RAID 0</button>
                 </div>
-
+                <!-- Not enough disks warning -->
+                <div id="notEnoughDisksWarning" class="ts-blankslate is-negative" style="display: none;">
+                    <div class="description" i18n>
+                        Not enough disks selected for the chosen RAID type. Please select more disks to proceed.
+                        // 選擇的磁碟不足以建立所選的 RAID 類型。請選擇更多磁碟以繼續。
+                    </div>
+                </div>
                 <!-- Space Estimation -->
                 <div class="ts-divider has-top-spaced-small has-bottom-spaced-small"></div>
                 <div class="ts-text" i18n>Usable Space
@@ -192,15 +198,42 @@
 <div class="ts-divider"></div>
 <div class="ts-content is-tertiary">
     <div class="ts-wrap is-end-aligned">
-        <button class="ts-button" i18n>Confirm
+        <button onclick="showConfirmNewRaidDialog();" class="ts-button" i18n>Confirm
             // 確認
         </button>
-        <button class="ts-button is-outlined" i18n>Cancel
+        <button onclick="cancelCreateNewRAIDArray(); //parent function" class="ts-button is-outlined" i18n>Cancel
             // 取消
         </button>
     </div>
 </div>
-
+<dialog id="new_raid_warning" class="ts-modal" style="background: rgba(0, 0, 0, 0.9);">
+    <div class="ts-content">
+        <div class="ts-header" i18n>Warning
+            // 警告
+        </div>
+        <div class="ts-text is-description" i18n>
+            Creating a new RAID array will format all selected disks and erase all data on them. This action cannot be undone. Are you sure you want to proceed?
+            // 建立新的 RAID 陣列將格式化所有選定的磁碟並清除其上的所有資料。此操作無法撤銷。您確定要繼續嗎?
+        </div>
+        <div class="ts-text is-description has-top-spaced-small">
+            <strong i18n>Selected Disks:
+                // 選定的磁碟:
+            </strong>
+            <span id="selected_disks_list"></span>
+        </div>
+        <div class="ts-wrap is-end-aligned has-top-spaced-large">
+            <button class="ts-button is-negative" onclick="createNewRAIDArray(true);" i18n>Proceed
+                // 繼續
+            </button>
+            <button class="ts-button is-negative is-outlined" onclick="createNewRAIDArray(false);" i18n>Proceed w/o superblock format
+                // 不格式化 superblock 繼續
+            </button>
+            <button class="ts-button is-outlined" onclick="document.getElementById('new_raid_warning').close();" i18n>Cancel
+                // 取消
+            </button>
+        </div>
+    </div>
+</dialog>
 <script>
 
     /* RAID name validation */
@@ -465,8 +498,7 @@
 
             totalUsableSpace = smallestDiskSize;
             totalRedundancySpace = smallestDiskSize * (totalDiskCount - 1);
-            totalWastedSpace = totalSize - totalUsableSpace;
-
+            totalWastedSpace = totalSize - totalUsableSpace - totalRedundancySpace;
             if (totalDiskCount >= 2){
                 diskCountIsEnough = true;
             }
@@ -513,7 +545,14 @@
             $("3capacityVisualizerInformationSlate").show();
             $("a[step='3']").removeClass("is-completed").addClass("is-active");
             $("a[step='4']").removeClass("is-active");
+            $("button[onclick='showConfirmNewRaidDialog();']").prop("disabled", true);
+            $("#notEnoughDisksWarning").show();
             return;
+        }else{
+            $("a[step='3']").removeClass("is-active").addClass("is-completed");
+            $("a[step='4']").addClass("is-active");
+            $("button[onclick='showConfirmNewRaidDialog();']").prop("disabled", false);
+            $("#notEnoughDisksWarning").hide();
         }
         //Update the visualizer
         let barMinWidth = 100;
@@ -538,4 +577,73 @@
         $("#estimatedWastedSpace").css("width", wastedSpacePercentage + "%");
 
     }
+
+    /* Reset Selections */
+    function resetNewRAIDUserInputs(){
+        // Reset RAID name
+        $("#raid_name").val("");
+        $("#raid_name").parent().removeClass("is-end-icon");
+        $("a[step='1']").removeClass("is-completed").removeClass("is-active");
+        $("a[step='2']").removeClass("is-active");
+        $(".ts-icon.raid_name_valid_icon").hide();
+
+        // Reset disk selection
+        $(".new-raid-disk-info").removeClass("selected");
+        $(".new_raid_disk_selected").hide();
+        $("a[step='2']").removeClass("is-completed").addClass("is-active");
+        $("a[step='3']").removeClass("is-active");
+
+        // Reset RAID type
+        $(".raid_type_selected").text("Select RAID Type");
+        $(".raid_type_selected").val("");
+        $("#new_raid_type_select .item").removeClass("is-active");
+        $("a[step='3']").removeClass("is-completed").addClass("is-active");
+
+        // Reset space estimation
+        $("#capacityVisualizer").hide();
+        $("#capacityVisualizerInformationSlate").show();
+        $("#estimatedUsableSpace").text("0%");
+        $("#estimatedProtectionSpace").text("0%");
+        $("#estimatedWastedSpace").text("0%");
+        $("#estimatedUsableSpace").css("width", "0%");
+        $("#estimatedProtectionSpace").css("width", "0%");
+        $("#estimatedWastedSpace").css("width", "0%");
+    }
+
+    /* Get the selection for new RAID arrays */
+    function getNewRAIDArraySelection(){
+        let selectedDisks = getCurrentSelectedDisks();
+        let selectedRAIDType = getCurrentSelectedRAIDType();
+        let raidName = $("#raid_name").val().trim();
+        return {
+            disks: selectedDisks,
+            type: selectedRAIDType,
+            name: raidName
+        };
+    }
+
+    function showConfirmNewRaidDialog(){
+        let selectedDisks = getCurrentSelectedDisks();
+        let selectedRAIDType = getCurrentSelectedRAIDType();
+        let raidName = $("#raid_name").val().trim();
+
+        //Check if the raid name is valid
+        if (raidName == "" || !/^[a-zA-Z0-9_-]+$/.test(raidName)){
+            $("#raid_name").parent().addClass("is-negative");
+            $(".new-raid-modal-content").animate({
+                scrollTop: $("#raid_name").offset().top - $(".new-raid-modal-content").offset().top
+            }, "smooth");
+            return;
+        }
+        let selectedDisksList = "";
+        selectedDisks.forEach(disk => {
+            selectedDisksList += "/dev/" + disk.name + " ";
+        });
+        $("#selected_disks_list").text(selectedDisksList);
+        $("#new_raid_warning")[0].showModal();
+    }
+
+    function hideConfirmNewRaidDialog(){
+        $("#new_raid_warning")[0].close();
+    }
 </script>

+ 8 - 0
web/js/locale.js

@@ -61,11 +61,19 @@ let translatedMessages = {
         "raid_resync_started_succ": 'RAID resync started',
         "raid_device_updated_succ": 'RAID device status reloaded',
         "raid_reassemble_started_succ": 'RAID config reloaded',
+        "raid_device_deleted_succ": 'RAID device deleted',
+        "raid_device_deleted_fail": 'RAID device delete failed',
+        "raid_device_created_succ": 'RAID device created',
+        "raid_device_created_fail": 'RAID device create failed',
     },
     'zh': {
         'disk_info_refreshed': '磁碟資訊已重新載入',
         "raid_resync_started_succ": 'RAID 重建已成功啟動',
         "raid_device_updated_succ": 'RAID 裝置狀態已重新載入',
         "raid_reassemble_started_succ": 'RAID 配置已重新載入',
+        "raid_device_deleted_succ": 'RAID 裝置已刪除',
+        "raid_device_deleted_fail": 'RAID 裝置刪除失敗',
+        "raid_device_created_succ": 'RAID 裝置已建立',
+        "raid_device_created_fail": 'RAID 裝置建立失敗',
     }
 };