|
@@ -82,7 +82,7 @@
|
|
|
border-radius: 0.4em !important;
|
|
|
margin-bottom: 0 !important;
|
|
|
margin-top: 0 !important;
|
|
|
- cursor: pointer;
|
|
|
+
|
|
|
position: relative;
|
|
|
}
|
|
|
.raiddevice:hover{
|
|
@@ -103,6 +103,21 @@
|
|
|
padding-right: 5px;
|
|
|
}
|
|
|
|
|
|
+ /* RAID memeber options */
|
|
|
+ .raidDeviceOptions{
|
|
|
+ width: 100%;
|
|
|
+ min-height: 2em;
|
|
|
+ }
|
|
|
+
|
|
|
+ .raidDeviceOptions .content{
|
|
|
+ float :right;
|
|
|
+ }
|
|
|
+
|
|
|
+ .notMountedLabel{
|
|
|
+ color:rgb(194, 194, 194);
|
|
|
+ font-weight: 300;
|
|
|
+ }
|
|
|
+
|
|
|
/* Danger zone css */
|
|
|
.dangerzone{
|
|
|
border: 1px solid #dedede;
|
|
@@ -126,13 +141,16 @@
|
|
|
</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;">
|
|
|
<div id="raidVolList">
|
|
@@ -143,13 +161,16 @@
|
|
|
<button title="Add new RAID volume" onclick="addNewRaidVolume()" class="circular basic green large ui icon button">
|
|
|
<i class="green add icon"></i>
|
|
|
</button>
|
|
|
+ <button title="Assemble RAID Pools" onclick="assembleRaidVolumes()" class="circular basic orange large ui icon button">
|
|
|
+ <i class="orange refresh icon"></i>
|
|
|
+ </button>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="ten wide column">
|
|
|
<div id="volumeDetail">
|
|
|
<div id="raidDiskOverview">
|
|
|
<h3 class="ui header">
|
|
|
- <i class="ui green check circle icon"></i>
|
|
|
+ <i id="RAIDOverviewStateIcon" class="ui green check circle icon"></i>
|
|
|
<div class="content">
|
|
|
<span id="RAIDOverviewDiskpath">Loading device details</span>
|
|
|
<div class="sub header" id="RAIDOverviewDetails"></div>
|
|
@@ -199,17 +220,36 @@
|
|
|
</div>
|
|
|
<br>
|
|
|
<div id="raidDiskList">
|
|
|
- <div class="ui basic segment">
|
|
|
- <h4 class="ui header">
|
|
|
- <img src="../disk/raid/img/drive-working.svg">
|
|
|
- <div class="content">
|
|
|
- /dev/sdb
|
|
|
- <div class="sub header">Check out our plug-in marketplace</div>
|
|
|
- </div>
|
|
|
- </h4>
|
|
|
- </div>
|
|
|
+
|
|
|
</div>
|
|
|
<div class="ui divider"></div>
|
|
|
+ <table class="ui very basic compact celled table" style="padding-left: 1em; padding-right: 1em;">
|
|
|
+ <tbody>
|
|
|
+ <tr>
|
|
|
+ <td>
|
|
|
+ <h4><i class="green add icon"></i> Add New Disk</h4>
|
|
|
+ <p>Add a new disk to the array. If your array have an empty slot (aka removed disk), the disk will be added as working disk, otherwise, it will be added as a spare disk.</p>
|
|
|
+ </td>
|
|
|
+ <td>
|
|
|
+ <button onclick="" class="circular small basic ui button">
|
|
|
+ <i class="green add icon"></i> Add Disk
|
|
|
+ </button>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ <tr>
|
|
|
+ <td>
|
|
|
+ <h4><i class="purple expand icon"></i> Expand RAID Volume</h4>
|
|
|
+ <p>If you have upgraded all disks in this RAID storage pool, you can grow the RAID volume and its file system to match the new size of your disks. </p>
|
|
|
+ </td>
|
|
|
+ <td>
|
|
|
+ <button onclick="" class="circular small basic ui button">
|
|
|
+ <i class="purple expand icon"></i> Expand Volume
|
|
|
+ </button>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+
|
|
|
<div class="dangerzone">
|
|
|
<div class="header">
|
|
|
<h4>Danger Zone</h4>
|
|
@@ -246,8 +286,87 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
+
|
|
|
+ <!-- Disk Remove Confirmation -->
|
|
|
+ <div id="confirmDiskRemove" class="ui mini modal">
|
|
|
+ <i class="close icon"></i>
|
|
|
+ <div class="header">
|
|
|
+ Confirm Disk Remove
|
|
|
+ </div>
|
|
|
+ <div class="image content">
|
|
|
+ <div class="ui medium image">
|
|
|
+ <img src="../disk/raid/img/remove-warning.svg">
|
|
|
+ </div>
|
|
|
+ <div class="description">
|
|
|
+ <p id="oprconfirm"></p>
|
|
|
+ <p><b>Removing a disk from RAID volume might risk data loss.</b> Make sure you know what you are doing.<br></p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="actions">
|
|
|
+ <div class="ui black deny button">
|
|
|
+ Cancel
|
|
|
+ </div>
|
|
|
+ <div class="ui negative left labeled icon button" onclick="confirmRemoveDisk();">
|
|
|
+ <i class="trash icon"></i>
|
|
|
+ REMOVE
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
<script>
|
|
|
|
|
|
+ var raidManager = {
|
|
|
+ editingArray: "",
|
|
|
+ removePendingDisk: "",
|
|
|
+ removePendingSourceVol: ""
|
|
|
+ };
|
|
|
+
|
|
|
+ function removeDisk(arrayName, diskPath){
|
|
|
+ console.log(arrayName, diskPath);
|
|
|
+ $("#oprconfirm").html("Remove <b>" + diskPath + "</b> from <b>" + arrayName + "</b>");
|
|
|
+ $("#confirmDiskRemove").modal('show');
|
|
|
+
|
|
|
+ raidManager.removePendingDisk = diskPath;
|
|
|
+ raidManager.removePendingSourceVol = arrayName;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ function confirmRemoveDisk(){
|
|
|
+ let raidName = raidManager.removePendingSourceVol.split("/").pop();
|
|
|
+ var apiObject = {
|
|
|
+ api: "../system/disk/raid/removeMemeber",
|
|
|
+ data: {
|
|
|
+ raidDev: raidManager.removePendingSourceVol,
|
|
|
+ memDev: raidManager.removePendingDisk
|
|
|
+ },
|
|
|
+ title: `<i class='yellow exclamation triangle icon'></i> REMOVE DISK FROM RAID VOLUME <i class='yellow exclamation triangle icon'></i>`,
|
|
|
+ desc: `Confirm remove ${raidManager.removePendingDisk} from ${raidManager.removePendingSourceVol}`,
|
|
|
+ thisuser: true, //This username as default, set to false for entering other user
|
|
|
+ method: "POST",
|
|
|
+ success: undefined
|
|
|
+ }
|
|
|
+ apiObject = encodeURIComponent(JSON.stringify(apiObject));
|
|
|
+
|
|
|
+
|
|
|
+ parent.newFloatWindow({
|
|
|
+ url: "SystemAO/security/authreq.html#" + apiObject,
|
|
|
+ width: 480,
|
|
|
+ height: 300,
|
|
|
+ appicon: "SystemAO/security/img/lock.svg",
|
|
|
+ title: `Confirm Disk Remove`,
|
|
|
+ parent: ao_module_windowID,
|
|
|
+ callback: "handleRemoveDiskCallback"
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ //Remove disk completed
|
|
|
+ window.handleRemoveDiskCallback = function(succ){
|
|
|
+ console.log(data);
|
|
|
+ setTimeout(function(){
|
|
|
+ reloadRAIDVolDetail();
|
|
|
+ }, 300);
|
|
|
+ }
|
|
|
+
|
|
|
//Quick function to check if the disk is healthy
|
|
|
function isHealthy(stateText) {
|
|
|
// Check if the stateText contains any unhealthy states
|
|
@@ -269,7 +388,7 @@
|
|
|
} else if (stateText.includes('recovering')) {
|
|
|
return 'ui yellow loading sync icon';
|
|
|
} else if (stateText.includes('degraded')) {
|
|
|
- return 'ui yellow exclamation triangle';
|
|
|
+ return 'ui yellow exclamation circle icon';
|
|
|
} else if (stateText.includes('inactive')) {
|
|
|
return 'ui grey check sync icon';
|
|
|
} else if (stateText.includes('active')) {
|
|
@@ -287,6 +406,13 @@
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ //Reload the current raid volume detail
|
|
|
+ function reloadRAIDVolDetail(){
|
|
|
+ if (raidManager.editingArray != undefined){
|
|
|
+ loadRAIDVolDetail(raidManager.editingArray);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
function loadRAIDVolDetail(deviceName){
|
|
|
function capitalize(string) {
|
|
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
|
@@ -300,10 +426,19 @@
|
|
|
return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i];
|
|
|
};
|
|
|
|
|
|
+ //Highlight the vol selected
|
|
|
+ $(".raidVol").removeClass('active');
|
|
|
+ $(".raidVol").each(function(){
|
|
|
+ if ($(this).attr("devpath") == deviceName){
|
|
|
+ $(this).addClass('active');
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
let deiviceSizeMap = {};
|
|
|
//Load the device size map
|
|
|
$.get("../../system/disk/raid/devinfo?devName=" + deviceName, function(sizemap){
|
|
|
deiviceSizeMap = sizemap;
|
|
|
+ raidManager.editingArray = deviceName;
|
|
|
//Get the information of the device
|
|
|
$.get("../../system/disk/raid/detail?devName=" + deviceName, function(data){
|
|
|
if (data.error != undefined){
|
|
@@ -318,8 +453,6 @@
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
-
|
|
|
-
|
|
|
//Update the active disks info
|
|
|
$("#RAIDOverviewDiskpath").html(data.DevicePath + ` <span class="mdevice">(${data.RaidLevel.toUpperCase()})</span>`);
|
|
|
$("#RAIDOverviewDetails").text(data.UUID + " | State: " + capitalize(data.State));
|
|
@@ -327,6 +460,7 @@
|
|
|
$("#RAIDWorkingDevices").text(data.WorkingDevices);
|
|
|
$("#RAIDFailedDevices").text(data.FailedDevices);
|
|
|
$("#RAIDSpareDevices").text(data.SpareDevices);
|
|
|
+ $("#RAIDOverviewStateIcon").attr("class", getStateIconFromStateText(data.State));
|
|
|
|
|
|
//Render the disk list
|
|
|
$("#raidDiskList").html("");
|
|
@@ -336,34 +470,89 @@
|
|
|
let drivePath = thisDeviceInfo.DevicePath
|
|
|
let driveName = drivePath.split("/").pop();
|
|
|
let driveIcon = "drive-working.svg";
|
|
|
+ let driveSize = "Unknown";
|
|
|
+ let driveRWState = "Read Write";
|
|
|
if (thisDeviceInfo.DevicePath = ""){
|
|
|
driveIcon = "drive-notfound.svg"
|
|
|
drivePath = "(Drive unplugged or damaged)"
|
|
|
}
|
|
|
|
|
|
+ let diskIsFailed = false;
|
|
|
+ if (thisDeviceInfo.State.includes("faulty")){
|
|
|
+ //Disk labeled as failed
|
|
|
+ driveIcon = "drive-failed.svg"
|
|
|
+ diskIsFailed = true;
|
|
|
+ }else if (thisDeviceInfo.State.includes("removed")){
|
|
|
+ driveIcon = "drive-notfound.svg"
|
|
|
+ }else if (thisDeviceInfo.State.includes("rebuilding")){
|
|
|
+ driveIcon = "drive-spare.svg"
|
|
|
+ }
|
|
|
+
|
|
|
+ if (deiviceSizeMap[driveName] == undefined){
|
|
|
+ //Disk not found. Is it failed?
|
|
|
+ driveSize = "";
|
|
|
+ driveRWState = "";
|
|
|
+ }else{
|
|
|
+ driveSize = bytesToSize(deiviceSizeMap[driveName].size);
|
|
|
+ driveRWState = deiviceSizeMap[driveName].ro?"<span class='readonlytag'>Read Only</span>":"Read Write";
|
|
|
+ }
|
|
|
|
|
|
- $("#raidDiskList").append(`<div class="ui basic segment raiddevice">
|
|
|
+ //Handle case where raidDeviceNo is -1 (Not mounted / failed)
|
|
|
+ if (raidDeviceNo == -1){
|
|
|
+ raidDeviceNo = "Failed";
|
|
|
+ }
|
|
|
+
|
|
|
+ //A random UID for this DOM element to make handling easier
|
|
|
+ let raiddeviceUID = Date.now();
|
|
|
+ $("#raidDiskList").append(`<div class="ui basic segment raiddevice ${raiddeviceUID}" domid="${raiddeviceUID}">
|
|
|
<h4 class="ui header">
|
|
|
<img src="../disk/raid/img/${driveIcon}">
|
|
|
<div class="content">
|
|
|
- ${raidDeviceNo}: ${drivePath}
|
|
|
+ ${raidDeviceNo}: <span class="raidMemberLabel ${driveName}"></span><br>
|
|
|
+ <small><i class="ui yellow folder icon" style="margin-right: 0.3em;"></i>${drivePath!=""?drivePath:`<span class="notMountedLabel">[Not Mounted]</span>`}</small>
|
|
|
<div class="sub header">${thisDeviceInfo.State.join(" | ")}</div>
|
|
|
</div>
|
|
|
</h4>
|
|
|
<div class="capinfo">
|
|
|
- ${bytesToSize(deiviceSizeMap[driveName].size)}<br>
|
|
|
- <span style="font-size: 0.8em;">${deiviceSizeMap[driveName].ro?"<span class='readonlytag'>Read Only</span>":"Read Write"}</span>
|
|
|
+ ${driveSize}<br>
|
|
|
+ <span style="font-size: 0.8em;">${driveRWState}</span>
|
|
|
</div>
|
|
|
<div class="raidDeviceOptions">
|
|
|
-
|
|
|
+ <div class="content">
|
|
|
+ <button class="ui tiny basic button"><i class="ui info circle grey icon"></i> SMART</button>
|
|
|
+ <button class="ui tiny basic button" onclick="removeDisk('${deviceName}','${driveName}');"><i class="ui red trash icon"></i> Remove Disk from Volume</button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
+ <div class="ui divider"></div>
|
|
|
</div>`);
|
|
|
+
|
|
|
+ if (driveName != "" && drivePath != ""){
|
|
|
+ $(".raidMemberLabel." + driveName).html(`<i class="ui loading circle notch icon"></i> Resolving disk label`);
|
|
|
+ resolveDiskLabelToDOM(drivePath, ".raidMemberLabel." + driveName);
|
|
|
+ }else{
|
|
|
+ //Invalid disk, e.g. Removed
|
|
|
+ $(".raiddevice." + raiddeviceUID).find(".raidDeviceOptions").hide();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
}
|
|
|
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+ function resolveDiskLabelToDOM(diskPath, domSelector){
|
|
|
+ $.get("/system/disk/devices/model?devName=" + diskPath, function(data){
|
|
|
+ let diskLabelName = ""
|
|
|
+ if (data.error == undefined){
|
|
|
+ //[0] is disk labeled name
|
|
|
+ //[1] is disk labeled size
|
|
|
+ diskLabelName = data[0];
|
|
|
+ }
|
|
|
+ $(domSelector).html(diskLabelName);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
//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>`);
|
|
@@ -377,9 +566,9 @@
|
|
|
let containUnhealthyDisk = false;
|
|
|
for (var i = 0; i < data.length; i++){
|
|
|
let thisDiskInfo = data[i];
|
|
|
- $("#raidVolList").append(`<div class="ui segment raidVol">
|
|
|
+ $("#raidVolList").append(`<div class="ui segment raidVol" devpath="${thisDiskInfo.DevicePath}">
|
|
|
<h4 class="ui header">
|
|
|
- <img src="../../img/system/raid.svg">
|
|
|
+ <img src="../disk/raid/img/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.toUpperCase()})</span>
|