|
@@ -18,6 +18,42 @@
|
|
|
.new-raid-disk-info:hover{
|
|
|
opacity: 0.5;
|
|
|
}
|
|
|
+
|
|
|
+ /* RAID Type space visualizer */
|
|
|
+ .raidTypeSpaceVisualizer{
|
|
|
+ width: 100%;
|
|
|
+ display: flex;
|
|
|
+ height: 42px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .raidTypeSpaceVisualizer .bars {
|
|
|
+ flex: 1; /* Occupy the remaining space */
|
|
|
+ display: flex; /* Nested flex container */
|
|
|
+ min-height: 2.6em;
|
|
|
+ border-radius: 0.4em;
|
|
|
+ overflow: hidden;
|
|
|
+ background-color: var(--ts-gray-500);
|
|
|
+ }
|
|
|
+
|
|
|
+ .raidTypeSpaceVisualizer .bar{
|
|
|
+ text-align: center;
|
|
|
+ float: left;
|
|
|
+ color: white;
|
|
|
+ padding-top: 0.6em;
|
|
|
+ }
|
|
|
+
|
|
|
+ .raidTypeSpaceVisualizer .bar.protected{
|
|
|
+ background-color: var(--ts-primary-500);
|
|
|
+ min-width: 100px;
|
|
|
+ }
|
|
|
+ .raidTypeSpaceVisualizer .bar.usable{
|
|
|
+ background-color: var(--ts-positive-400);
|
|
|
+ min-width: 100px;
|
|
|
+ }
|
|
|
+ .raidTypeSpaceVisualizer .bar.wasted{
|
|
|
+ background-color: var(--ts-gray-500);
|
|
|
+ width: 0%;
|
|
|
+ }
|
|
|
</style>
|
|
|
<div class="content">
|
|
|
<div class="ts-content">
|
|
@@ -114,6 +150,39 @@
|
|
|
<button class="item raid_type" value="raid5">RAID 5 </button>
|
|
|
<button class="item raid_type" value="raid0">RAID 0</button>
|
|
|
</div>
|
|
|
+
|
|
|
+ <!-- Space Estimation -->
|
|
|
+ <div class="ts-divider has-top-spaced-small has-bottom-spaced-small"></div>
|
|
|
+ <div class="ts-text" i18n>Usable Space
|
|
|
+ // 可使用空間
|
|
|
+ </div>
|
|
|
+ <div class="ts-blankslate" id="capacityVisualizerInformationSlate" style="pointer-events: none; user-select: none; opacity: 0.5;">
|
|
|
+ <div class="description" i18n> Select disks and RAID type to estimate usable space.
|
|
|
+ // 選擇磁碟和陣列類型以估算可用空間。
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div id="capacityVisualizer" class="ts-content" style="display: none;">
|
|
|
+ <div class="raidTypeSpaceVisualizer">
|
|
|
+ <div class="bars">
|
|
|
+ <div class="bar usable" id="estimatedUsableSpace">0%</div>
|
|
|
+ <div class="bar protected" id="estimatedProtectionSpace">0%</div>
|
|
|
+ <div class="bar wasted" id="estimatedWastedSpace">0%</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+ <div class="ts-content" style="float: right;">
|
|
|
+ <div class="ts-badge is-spaced-small" style="background-color: var(--ts-positive-400); color: white;" i18n>Available Space
|
|
|
+ // 可用空間
|
|
|
+ </div>
|
|
|
+ <div class="ts-badge is-spaced-small" style="background-color: var(--ts-primary-500); color: white;" i18n>Redundancy
|
|
|
+ // 冗餘
|
|
|
+ </div>
|
|
|
+ <div class="ts-badge is-spaced-small" style="background-color: var(--ts-gray-500); color: white;" i18n>Unused
|
|
|
+ // 未使用
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <br>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
@@ -178,6 +247,7 @@
|
|
|
for (var i = 0; i < disks.length; i++) {
|
|
|
let disk = disks[i];
|
|
|
let partitionTable = "";
|
|
|
+ let encodedDiskInfo = encodeURIComponent(JSON.stringify(disk));
|
|
|
let diskIsMounted = false;
|
|
|
let diskIsAlreadyRAID = false; //The disk already belongs to another RAID array
|
|
|
// Render the partition table
|
|
@@ -237,7 +307,7 @@
|
|
|
}
|
|
|
|
|
|
// Append disk information with partition table
|
|
|
- diskList.append(`<div class="ts-box ts-content has-top-spaced-small new-raid-disk-info ${warningClass} ${disabledClass}">
|
|
|
+ diskList.append(`<div class="ts-box ts-content has-top-spaced-small new-raid-disk-info ${warningClass} ${disabledClass}" data-disk-info='${encodedDiskInfo}'>
|
|
|
${diskIsMounted ? warningMessage : ""}
|
|
|
<div class="ts-item">
|
|
|
<div class="ts-header">${disk.model} <span class="ts-badge has-start-spaced-small">${humanFileSize(disk.size)}</span></div>
|
|
@@ -280,6 +350,9 @@
|
|
|
$("a[step='2']").removeClass("is-completed").addClass("is-active");
|
|
|
$("a[step='3']").removeClass("is-active");
|
|
|
}
|
|
|
+
|
|
|
+ //Render the space estimation if any disk is selected
|
|
|
+ renderNewRaidSpaceEstimation();
|
|
|
});
|
|
|
relocale();
|
|
|
});
|
|
@@ -306,8 +379,163 @@
|
|
|
$(".raid_type").removeAttr("aria-hidden");
|
|
|
$(this).attr("aria-hidden", "false");
|
|
|
|
|
|
+ //Write the selected type to attribute
|
|
|
+ var selectedRAIDType = $(this).val();
|
|
|
+ $(".raid_type_selected").attr("value", selectedRAIDType);
|
|
|
+
|
|
|
// Mark step 3 as completed and step 2 as active
|
|
|
$("a[step='3']").removeClass("is-active").addClass("is-completed");
|
|
|
$("a[step='2']").addClass("is-active");
|
|
|
+
|
|
|
+ renderNewRaidSpaceEstimation();
|
|
|
});
|
|
|
+
|
|
|
+ function getCurrentSelectedDisks(){
|
|
|
+ var selectedDisks = [];
|
|
|
+ $(".new-raid-disk-info.selected").each(function() {
|
|
|
+ var diskInfo = $(this).attr("data-disk-info");
|
|
|
+ var disk = JSON.parse(decodeURIComponent(diskInfo));
|
|
|
+ selectedDisks.push(disk);
|
|
|
+ });
|
|
|
+ return selectedDisks;
|
|
|
+ }
|
|
|
+
|
|
|
+ function getCurrentSelectedRAIDType(){
|
|
|
+ var selectedRAIDType = $(".raid_type_selected").val();
|
|
|
+ if (selectedRAIDType == undefined || selectedRAIDType == "") {
|
|
|
+ selectedRAIDType = "";
|
|
|
+ }
|
|
|
+ return selectedRAIDType;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /* Render the space estimation */
|
|
|
+ function renderNewRaidSpaceEstimation(){
|
|
|
+ function bytesToHumanReadable(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];
|
|
|
+ }
|
|
|
+ //Check if any disk is selected and raid type is selected
|
|
|
+ let selectedDisks = getCurrentSelectedDisks();
|
|
|
+ let selectedRAIDType = getCurrentSelectedRAIDType();
|
|
|
+ if (selectedDisks.length == 0 || selectedRAIDType == "") {
|
|
|
+ $("#capacityVisualizer").hide();
|
|
|
+ $("3capacityVisualizerInformationSlate").show();
|
|
|
+ return;
|
|
|
+ } else {
|
|
|
+ $("#capacityVisualizer").show();
|
|
|
+ $("#capacityVisualizerInformationSlate").hide();
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(selectedDisks, selectedRAIDType);
|
|
|
+
|
|
|
+ //Calculate the total size of selected disks
|
|
|
+ let totalSize = 0;
|
|
|
+ let totalUsableSpace = 0;
|
|
|
+ let totalRedundancySpace = 0;
|
|
|
+ let totalWastedSpace = 0;
|
|
|
+ let totalDiskCount = selectedDisks.length;
|
|
|
+ let diskCountIsEnough = false;
|
|
|
+ if (selectedRAIDType == "raid0"){
|
|
|
+ // RAID 0: No redundancy, all space is usable
|
|
|
+ // Usable space is determined by the smallest disk size * the number of disks
|
|
|
+ let smallestDiskSize = 0;
|
|
|
+ selectedDisks.forEach(disk => {
|
|
|
+ totalSize += disk.size;
|
|
|
+ if (smallestDiskSize == 0 || disk.size < smallestDiskSize) {
|
|
|
+ smallestDiskSize = disk.size;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ totalUsableSpace = smallestDiskSize * totalDiskCount;
|
|
|
+ totalRedundancySpace = 0;
|
|
|
+ totalWastedSpace = totalSize - totalUsableSpace;
|
|
|
+ if (totalDiskCount >= 2){
|
|
|
+ diskCountIsEnough = true;
|
|
|
+ }
|
|
|
+ }else if (selectedRAIDType == "raid1"){
|
|
|
+ // RAID 1: Mirroring, usable space is the size of the smallest disk
|
|
|
+ let smallestDiskSize = 0;
|
|
|
+ selectedDisks.forEach(disk => {
|
|
|
+ totalSize += disk.size;
|
|
|
+ if (smallestDiskSize == 0 || disk.size < smallestDiskSize) {
|
|
|
+ smallestDiskSize = disk.size;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ totalUsableSpace = smallestDiskSize;
|
|
|
+ totalRedundancySpace = smallestDiskSize * (totalDiskCount - 1);
|
|
|
+ totalWastedSpace = totalSize - totalUsableSpace;
|
|
|
+
|
|
|
+ if (totalDiskCount >= 2){
|
|
|
+ diskCountIsEnough = true;
|
|
|
+ }
|
|
|
+ }else if (selectedRAIDType == "raid5"){
|
|
|
+ // RAID 5: Striping with parity, usable space is total size - size of one disk
|
|
|
+ let smallestDiskSize = 0;
|
|
|
+ selectedDisks.forEach(disk => {
|
|
|
+ totalSize += disk.size;
|
|
|
+ if (smallestDiskSize == 0 || disk.size < smallestDiskSize) {
|
|
|
+ smallestDiskSize = disk.size;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ totalUsableSpace = smallestDiskSize * (totalDiskCount - 1);
|
|
|
+ totalRedundancySpace = smallestDiskSize;
|
|
|
+ totalWastedSpace = totalSize - totalUsableSpace - totalRedundancySpace;
|
|
|
+ if (totalDiskCount >= 3){
|
|
|
+ diskCountIsEnough = true;
|
|
|
+ }
|
|
|
+ }else if (selectedRAIDType == "raid6"){
|
|
|
+ // RAID 6: Striping with double parity, usable space is total size - size of two disks
|
|
|
+ let smallestDiskSize = 0;
|
|
|
+ selectedDisks.forEach(disk => {
|
|
|
+ totalSize += disk.size;
|
|
|
+ if (smallestDiskSize == 0 || disk.size < smallestDiskSize) {
|
|
|
+ smallestDiskSize = disk.size;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ totalUsableSpace = smallestDiskSize * (totalDiskCount - 2);
|
|
|
+ totalRedundancySpace = smallestDiskSize * 2;
|
|
|
+ totalWastedSpace = totalSize - totalUsableSpace - totalRedundancySpace;
|
|
|
+ if (totalDiskCount >= 4){
|
|
|
+ diskCountIsEnough = true;
|
|
|
+ }
|
|
|
+ }else{
|
|
|
+ console.error("Unknown RAID type: " + selectedRAIDType);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ //Check if the disk count is enough for the selected RAID type
|
|
|
+ if (!diskCountIsEnough){
|
|
|
+ $("#capacityVisualizer").hide();
|
|
|
+ $("3capacityVisualizerInformationSlate").show();
|
|
|
+ $("a[step='3']").removeClass("is-completed").addClass("is-active");
|
|
|
+ $("a[step='4']").removeClass("is-active");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ //Update the visualizer
|
|
|
+ let barMinWidth = 100;
|
|
|
+ let usableSpacePercentage = (totalUsableSpace / totalSize) * 100;
|
|
|
+ let redundancySpacePercentage = (totalRedundancySpace / totalSize) * 100;
|
|
|
+ let wastedSpacePercentage = (totalWastedSpace / totalSize) * 100;
|
|
|
+ $("#estimatedUsableSpace").text(bytesToHumanReadable(totalUsableSpace));
|
|
|
+ $("#estimatedProtectionSpace").text(bytesToHumanReadable(totalRedundancySpace));
|
|
|
+ $("#estimatedWastedSpace").text(bytesToHumanReadable(totalWastedSpace));
|
|
|
+ $("#estimatedUsableSpace").css("width", usableSpacePercentage + "%");
|
|
|
+ if (usableSpacePercentage == 0){
|
|
|
+ $("#estimatedUsableSpace").css("min-width", "0");
|
|
|
+ }else{
|
|
|
+ $("#estimatedUsableSpace").css("min-width", barMinWidth + "px");
|
|
|
+ }
|
|
|
+ $("#estimatedProtectionSpace").css("width", redundancySpacePercentage + "%");
|
|
|
+ if (redundancySpacePercentage == 0 ){
|
|
|
+ $("#estimatedProtectionSpace").css("min-width", "0");
|
|
|
+ }else{
|
|
|
+ $("#estimatedProtectionSpace").css("min-width", barMinWidth + "px");
|
|
|
+ }
|
|
|
+ $("#estimatedWastedSpace").css("width", wastedSpacePercentage + "%");
|
|
|
+
|
|
|
+ }
|
|
|
</script>
|