|
@@ -0,0 +1,313 @@
|
|
|
+<!-- This will be shown in a ts-modal in the raid.html-->
|
|
|
+<style>
|
|
|
+ .new-raid-modal-content{
|
|
|
+ max-height: 70vh;
|
|
|
+ overflow-y: auto;
|
|
|
+ }
|
|
|
+ @media screen and (max-width: 767px) {
|
|
|
+ .new-raid-modal-content {
|
|
|
+ max-height: none;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .new-raid-disk-info{
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ .new-raid-disk-info:hover{
|
|
|
+ opacity: 0.5;
|
|
|
+ }
|
|
|
+</style>
|
|
|
+<div class="content">
|
|
|
+<div class="ts-content">
|
|
|
+ <div class="ts-header" i18n>Create New RAID Array
|
|
|
+ // 建立 RAID 陣列
|
|
|
+ </div>
|
|
|
+</div>
|
|
|
+<div class="ts-divider"></div>
|
|
|
+<div class="ts-content new-raid-modal-content">
|
|
|
+ <div class="ts-grid mobile:is-stacked" >
|
|
|
+ <div class="column is-3-wide">
|
|
|
+ <div class="ts-procedure is-vertical has-top-spaced-large" style="position: sticky; top: 0;">
|
|
|
+ <a step="1" class="item is-active">
|
|
|
+ <div class="content">
|
|
|
+ <div class="indicator"></div>
|
|
|
+ <div class="label" i18n>RAID Name
|
|
|
+ // 陣列名稱
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </a>
|
|
|
+ <a step="2" class="item">
|
|
|
+ <div class="content">
|
|
|
+ <div class="indicator"></div>
|
|
|
+ <div class="label" i18n>Select Disks
|
|
|
+ // 選擇磁碟
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </a>
|
|
|
+ <a step="3" class="item">
|
|
|
+ <div class="content">
|
|
|
+ <div class="indicator"></div>
|
|
|
+ <div class="label" i18n>Raid Type
|
|
|
+ // 陣列類型
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </a>
|
|
|
+ <a step="4" class="item">
|
|
|
+ <div class="content">
|
|
|
+ <div class="indicator"></div>
|
|
|
+ <div class="label" i18n>Confirm
|
|
|
+ // 確認設定
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </a>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="column is-13-wide" style="overflow-y: auto;">
|
|
|
+ <div class="ts-content">
|
|
|
+ <!-- Array Name -->
|
|
|
+ <div class="ts-text" i18n>RAID Name
|
|
|
+ // 陣列名稱
|
|
|
+ </div>
|
|
|
+ <div class="ts-input has-top-spaced-small">
|
|
|
+ <input type="text" placeholder="my-raid" id="raid_name" i18n>
|
|
|
+ <span class="ts-icon is-circle-check-icon raid_name_valid_icon" style="color: var(--ts-positive-400); display:none;"></span>
|
|
|
+ </div>
|
|
|
+ <div class="ts-text is-description" i18n>Only alphabet, digits, _ (underscore) and - (hyphen) are allowed
|
|
|
+ // 只允許字母、數字、_ (底線) 和 - (連字符)
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Select Disks -->
|
|
|
+ <div class="ts-divider has-top-spaced-small has-bottom-spaced-large"></div>
|
|
|
+ <div id="new_raid_disk_select">
|
|
|
+ <div class="ts-content">
|
|
|
+ <div class="ts-text is-description" i18n>Loading disks, please wait...
|
|
|
+ // 正在載入磁碟,請稍候...
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="ts-text is-description has-top-spaced-small" i18n> Tips: For any extra disks selected, it will be used as spare disks.
|
|
|
+ // 提示:選擇的任何額外磁碟將用作備用磁碟。
|
|
|
+ </div>
|
|
|
+ <div class="ts-wrap is-end-aligned">
|
|
|
+ <button class="ts-button is-outlined" i18n>Refresh
|
|
|
+ // 重新整理
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Array Type -->
|
|
|
+ <div class="ts-divider has-top-spaced-small has-bottom-spaced-small"></div>
|
|
|
+ <div class="ts-text" i18n>RAID Type
|
|
|
+ // 陣列類型
|
|
|
+ </div>
|
|
|
+ <button class="ts-button is-start-icon has-top-spaced-small" data-dropdown="new_raid_type_select">
|
|
|
+ <span class="ts-icon is-chevron-down-icon"></span>
|
|
|
+ <span class="raid_type_selected" i18n>Select RAID Type
|
|
|
+ // 選擇陣列類型
|
|
|
+ </span>
|
|
|
+ </button>
|
|
|
+ <div class="ts-dropdown is-end-icon" id="new_raid_type_select">
|
|
|
+ <button class="item raid_type" value="raid1">RAID 1 <span class="ts-icon is-star-icon" style="color: var(--ts-warning-400);"></span></button>
|
|
|
+ <button class="item raid_type" value="raid6">RAID 6 <span class="ts-icon is-star-icon" style="color: var(--ts-warning-400);"></span></button>
|
|
|
+ <button class="item raid_type" value="raid5">RAID 5 </button>
|
|
|
+ <button class="item raid_type" value="raid0">RAID 0</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+</div>
|
|
|
+<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>
|
|
|
+ <button class="ts-button is-outlined" i18n>Cancel
|
|
|
+ // 取消
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+</div>
|
|
|
+
|
|
|
+<script>
|
|
|
+
|
|
|
+ /* RAID name validation */
|
|
|
+ $("#raid_name").on("keydown", function() {
|
|
|
+ validateRaidName();
|
|
|
+ });
|
|
|
+
|
|
|
+ $("#raid_name").on("change", function() {
|
|
|
+ validateRaidName();
|
|
|
+ });
|
|
|
+
|
|
|
+ function validateRaidName(){
|
|
|
+ var raidName = $("#raid_name").val().trim();
|
|
|
+ var isValid = /^[a-zA-Z0-9_-]+$/.test(raidName) && raidName.trim() !== "";
|
|
|
+
|
|
|
+ if (raidName == ""){
|
|
|
+ $("#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();
|
|
|
+ } else if (isValid) {
|
|
|
+ $("#raid_name").parent().removeClass("is-negative");
|
|
|
+ $("a[step='1']").removeClass("is-active").addClass("is-completed");
|
|
|
+ $("a[step='2']").addClass("is-active");
|
|
|
+ $("#raid_name").parent().addClass("is-end-icon");
|
|
|
+ $(".ts-icon.raid_name_valid_icon").show();
|
|
|
+ } else {
|
|
|
+ $("#raid_name").parent().addClass("is-negative");
|
|
|
+ $("a[step='1']").removeClass("is-completed").addClass("is-active");
|
|
|
+ $("a[step='2']").removeClass("is-active");
|
|
|
+ $("#raid_name").parent().removeClass("is-end-icon");
|
|
|
+ $(".ts-icon.raid_name_valid_icon").hide();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Disk selection */
|
|
|
+ function initNewRAIDDiskList(){
|
|
|
+ $.get("./api/info/list", function(data) {
|
|
|
+ if (data) {
|
|
|
+ var disks = data;
|
|
|
+ var diskList = $("#new_raid_disk_select");
|
|
|
+ diskList.empty();
|
|
|
+
|
|
|
+ for (var i = 0; i < disks.length; i++) {
|
|
|
+ let disk = disks[i];
|
|
|
+ let partitionTable = "";
|
|
|
+ let diskIsMounted = false;
|
|
|
+ let diskIsAlreadyRAID = false; //The disk already belongs to another RAID array
|
|
|
+ // Render the partition table
|
|
|
+ if (disk.partitions.length > 0) {
|
|
|
+ partitionTable += `<table class="ts-table is-bordered is-striped">
|
|
|
+ <thead>
|
|
|
+ <tr>
|
|
|
+ <th i18n>Partition Name
|
|
|
+ // 分割區
|
|
|
+ </th>
|
|
|
+ <th i18n>Size
|
|
|
+ // 大小
|
|
|
+ </th>
|
|
|
+ <th i18n>Type
|
|
|
+ // 類型
|
|
|
+ </th>
|
|
|
+ <th i18n>Mount Point
|
|
|
+ // 掛載點
|
|
|
+ </th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody>`;
|
|
|
+ for (var j = 0; j < disk.partitions.length; j++) {
|
|
|
+ let partition = disk.partitions[j];
|
|
|
+ partitionTable += `<tr>
|
|
|
+ <td>${partition.name}</td>
|
|
|
+ <td>${humanFileSize(partition.size)}</td>
|
|
|
+ <td>${partition.fstype || partition.blocktype}</td>
|
|
|
+ <td>${partition.mountpoint || ""}</td>
|
|
|
+ </tr>`;
|
|
|
+ if (partition.mountpoint) {
|
|
|
+ diskIsMounted = true;
|
|
|
+ }
|
|
|
+ if (partition.blocktype.includes("raid")) {
|
|
|
+ diskIsAlreadyRAID = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ partitionTable += `</tbody></table>`;
|
|
|
+ } else {
|
|
|
+ partitionTable = `<div class="ts-text is-description" i18n>No Partitions
|
|
|
+ // 無分割區
|
|
|
+ </div>`;
|
|
|
+ }
|
|
|
+
|
|
|
+ let warningClass = "";
|
|
|
+ let warningMessage = `<div class="ts-text is-negative" i18n>
|
|
|
+ This disk is mounted and might be in use.
|
|
|
+ // 此磁碟已被掛載,可能正在使用中。
|
|
|
+ </div>`;
|
|
|
+ if (diskIsMounted) {
|
|
|
+ warningClass = "is-negative is-start-indicated";
|
|
|
+ }
|
|
|
+
|
|
|
+ let disabledClass = "";
|
|
|
+ if (diskIsAlreadyRAID) {
|
|
|
+ disabledClass = "is-disabled";
|
|
|
+ }
|
|
|
+
|
|
|
+ // Append disk information with partition table
|
|
|
+ diskList.append(`<div class="ts-box ts-content has-top-spaced-small new-raid-disk-info ${warningClass} ${disabledClass}">
|
|
|
+ ${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>
|
|
|
+ <div class="ts-text is-description">
|
|
|
+ /dev/${disk.name}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="has-top-spaced-small">
|
|
|
+ ${partitionTable}
|
|
|
+ </div>
|
|
|
+ <div class="new_raid_disk_selected">
|
|
|
+ <span class="ts-icon is-circle-check-icon" style="color: var(--ts-positive-400); font-size: 1.5rem;"></span>
|
|
|
+ </div>
|
|
|
+ </div>`);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ console.error("Failed to load disk info: " + data.message);
|
|
|
+ }
|
|
|
+
|
|
|
+ //Bind click event to each disk info
|
|
|
+ $(".new-raid-disk-info").off("click").on("click", function() {
|
|
|
+ var selectedDisk = $(this);
|
|
|
+ var selectedIcon = selectedDisk.find(".new_raid_disk_selected");
|
|
|
+ var isSelected = $(this).hasClass("selected");
|
|
|
+ if (isSelected) {
|
|
|
+ selectedDisk.removeClass("selected");
|
|
|
+ selectedIcon.hide();
|
|
|
+ } else {
|
|
|
+ selectedDisk.addClass("selected");
|
|
|
+ selectedIcon.show();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check if any disk is selected
|
|
|
+ if ($(".new-raid-disk-info.selected").length > 0) {
|
|
|
+ // Mark step 2 as completed and step 3 as active
|
|
|
+ $("a[step='2']").removeClass("is-active").addClass("is-completed");
|
|
|
+ $("a[step='3']").addClass("is-active");
|
|
|
+ } else {
|
|
|
+ // Restore the not complete state of the steps
|
|
|
+ $("a[step='2']").removeClass("is-completed").addClass("is-active");
|
|
|
+ $("a[step='3']").removeClass("is-active");
|
|
|
+ }
|
|
|
+ });
|
|
|
+ relocale();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ initNewRAIDDiskList();
|
|
|
+
|
|
|
+ /* RAID type selection */
|
|
|
+ $(".raid_type").click(function() {
|
|
|
+ var raid_type = $(this).text();
|
|
|
+ var raid_type_value = $(this).val();
|
|
|
+ $(".raid_type_selected").text(raid_type);
|
|
|
+ $(".raid_type_selected").val(raid_type_value);
|
|
|
+ $("#new_raid_type_select").removeClass("is-active");
|
|
|
+ $("#new_raid_type_select").removeAttr("data-dropdown");
|
|
|
+ $("#new_raid_type_select").attr("data-dropdown", "new_raid_type_select");
|
|
|
+ $(".raid_type").removeClass("is-active");
|
|
|
+ $(this).addClass("is-active");
|
|
|
+ $(".raid_type").removeAttr("aria-pressed");
|
|
|
+ $(this).attr("aria-pressed", "true");
|
|
|
+ $(".raid_type").removeAttr("aria-selected");
|
|
|
+ $(this).attr("aria-selected", "true");
|
|
|
+ $(".raid_type").removeAttr("aria-expanded");
|
|
|
+ $(this).attr("aria-expanded", "true");
|
|
|
+ $(".raid_type").removeAttr("aria-hidden");
|
|
|
+ $(this).attr("aria-hidden", "false");
|
|
|
+
|
|
|
+ // 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");
|
|
|
+ });
|
|
|
+</script>
|