|
@@ -1,3 +1,9 @@
|
|
|
+<style>
|
|
|
+ #activate_raid_btn{
|
|
|
+ background: var(--ts-positive-400) !important;
|
|
|
+ border: 0px solid transparent !important;
|
|
|
+ }
|
|
|
+</style>
|
|
|
<div class="ts-content">
|
|
|
<div class="ts-container is-padded">
|
|
|
<div class="ts-grid mobile:is-stacked">
|
|
@@ -38,16 +44,7 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
<script>
|
|
|
-
|
|
|
-
|
|
|
function initRAIDDeviceList(){
|
|
|
- // 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];
|
|
|
- }
|
|
|
$.ajax({
|
|
|
url: './api/raid/list',
|
|
|
type: 'GET',
|
|
@@ -59,136 +56,29 @@
|
|
|
console.error('Error fetching RAID devices:', data.error);
|
|
|
$('#raid_array_list').append('<div class="ts-text is-error">Error: ' + data.error + '</div>');
|
|
|
}else{
|
|
|
- let isSyncing = false;
|
|
|
data.forEach((raid, index) => {
|
|
|
- // Add a new menu item for each RAID array
|
|
|
- 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>';
|
|
|
- } else if (raid.State.includes('sync')) {
|
|
|
- isSyncing = true;
|
|
|
- icon = '<span class="ts-icon is-spinning is-rotate-icon" style="color: var(--ts-positive-500);"></span>';
|
|
|
- } else if (raid.State.includes('degraded')) {
|
|
|
- icon = '<span class="ts-icon is-triangle-exclamation-icon" style="color: var(--ts-warning-600);"></span>';
|
|
|
- } else if (raid.State.includes('fail')) {
|
|
|
- icon = '<span class="ts-icon is-circle-xmark-icon" style="color: var(--ts-negative-500);"></span>';
|
|
|
- } else {
|
|
|
- icon = '<span class="ts-icon is-question-icon" style="color: var(--ts-gray-500);"></span>';
|
|
|
- }
|
|
|
- const menuItem = `
|
|
|
- <a class="raid-array item ${index==0?'is-active':''}" id="raid_menu_${index}" onclick="showRAIDDetails(${index})">
|
|
|
- ${icon}
|
|
|
- <div class="ts-content is-dense">
|
|
|
- <div>
|
|
|
- <span class="ts-text is-heavy">${raid.DevicePath}</span> | ${raid.RaidLevel.toUpperCase()}
|
|
|
- </div>
|
|
|
- <div class="ts-text is-tiny has-top-spaced-small">
|
|
|
- ${raid.Name}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </a>
|
|
|
- `;
|
|
|
- $('#raid_array_list').append(menuItem);
|
|
|
-
|
|
|
- // Add a hidden div for each RAID array's details
|
|
|
- const raidDetails = `
|
|
|
- <div id="raid_details_${index}" class="raid-details" style="display: none ;">
|
|
|
- <div class="ts-box">
|
|
|
- <div class="ts-content is-padded">
|
|
|
- <div class="ts-header is-start-icon">
|
|
|
- ${icon}
|
|
|
- ${raid.DevicePath} | ${raid.RaidLevel.toUpperCase()}
|
|
|
- </div>
|
|
|
- <div class="ts-text is-description">
|
|
|
- ${raid.UUID}<br>
|
|
|
- ${raid.Name}
|
|
|
- </div>
|
|
|
- <div class="ts-text">
|
|
|
- <span i18n> State
|
|
|
- // 狀態
|
|
|
- </span>: ${raid.State}<br>
|
|
|
- <div class="sync-progress has-top-spaced-small ${isSyncing?'need-update-raid-sync-progress':''}" devname="${raid.DevicePath}" style="display: ${isSyncing?"auto":"none"};">
|
|
|
- <div class="ts-progress is-processing">
|
|
|
- <div class="bar" style="--value: 0">
|
|
|
- <div class="text">0%</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="ts-text is-description has-top-spaced-small">
|
|
|
- <span i18n> Synchronized
|
|
|
- // 已處理</span>
|
|
|
- <span class="processed_blocks"></span>
|
|
|
- <span>/</span>
|
|
|
- <span class="total_blocks"></span>
|
|
|
- <span i18n> blocks
|
|
|
- // 個區塊
|
|
|
- </span><br>
|
|
|
- <!-- <span i18n> Speed
|
|
|
- // 速度
|
|
|
- </span>: <span class="speed"></span><br>
|
|
|
- <span i18n> Expected Time
|
|
|
- // 預估時間
|
|
|
- </span>: <span class="expected_time"></span>
|
|
|
- -->
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <span i18n> Array Size
|
|
|
- // 陣列大小
|
|
|
- </span>: ${bytesToHumanReadable(raid.ArraySize * 1024)}<br>
|
|
|
- <span i18n> Created
|
|
|
- // 建立時間
|
|
|
- </span>: <span>${new Date(raid.CreationTime).toLocaleString()}</span><br>
|
|
|
- </div>
|
|
|
- <table class="ts-table is-single-line has-top-spaced-large">
|
|
|
- <thead>
|
|
|
- <tr>
|
|
|
- <th i18n>Disk Status
|
|
|
- // 磁碟狀態
|
|
|
- </th>
|
|
|
- <th i18n>Counts
|
|
|
- // 數量
|
|
|
- </th>
|
|
|
- </tr>
|
|
|
- </thead>
|
|
|
- <tbody>
|
|
|
- <tr>
|
|
|
- <td i18n> Active Devices
|
|
|
- // 啟用的磁碟
|
|
|
- </td>
|
|
|
- <td>${raid.ActiveDevices}</td>
|
|
|
- </tr>
|
|
|
- <tr>
|
|
|
- <td i18n> Working Devices
|
|
|
- // 工作中的磁碟
|
|
|
- </td>
|
|
|
- <td>${raid.WorkingDevices}</td>
|
|
|
- </tr>
|
|
|
- <tr>
|
|
|
- <td i18n> Failed Devices
|
|
|
- // 故障的磁碟
|
|
|
- </td>
|
|
|
- <td>${raid.FailedDevices}</td>
|
|
|
- </tr>
|
|
|
- <tr>
|
|
|
- <td i18n> Spare Devices
|
|
|
- // 備用磁碟
|
|
|
- </td>
|
|
|
- <td>${raid.SpareDevices}</td>
|
|
|
- </tr>
|
|
|
- </tbody>
|
|
|
- </table>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="ts-box is-padded has-top-spaced-small">
|
|
|
- <div class="ts-content">
|
|
|
-
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- `;
|
|
|
- $('#raid_details').append(raidDetails);
|
|
|
+ let raidDetails = renderRAIDPoolDetail(raid, index);
|
|
|
+ $("#raid_array_list").append(raidDetails[0]);
|
|
|
+ $('#raid_details').append(raidDetails[1]);
|
|
|
});
|
|
|
+
|
|
|
+ if (data.length == 0){
|
|
|
+ $('#raid_array_list').append(`
|
|
|
+ <div class="ts-blankslate" style="pointer-events: none; user-select: none; opacity: 0.7;">
|
|
|
+ <div class="description">
|
|
|
+ <span class="ts-icon is-circle-check-icon" style="color: var(--ts-positive-400);"></span>
|
|
|
+ <span class="has-start-spaced-small" i18n> No RAID array found.
|
|
|
+ // 沒有 RAID 陣列
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>`);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+ // Show the first RAID details by default
|
|
|
+ if (data.length > 0) {
|
|
|
+ showRAIDDetails(0);
|
|
|
+ }
|
|
|
relocale(); // Recalculate layout
|
|
|
syncProgressTicker(); // Start the sync progress ticker
|
|
|
},
|
|
@@ -197,9 +87,243 @@
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
-
|
|
|
initRAIDDeviceList();
|
|
|
|
|
|
+ function updateRAIDArrayStatus(devname){
|
|
|
+ if (devname.startsWith('/dev/')) {
|
|
|
+ devname = devname.slice(5);
|
|
|
+ }
|
|
|
+ $.ajax({
|
|
|
+ url: './api/raid/info?dev=' + devname,
|
|
|
+ type: 'GET',
|
|
|
+ dataType: 'json',
|
|
|
+ success: function(data) {
|
|
|
+ if (data.error != undefined){
|
|
|
+ // Handle error response
|
|
|
+ console.error('Error fetching RAID status:', data.error);
|
|
|
+ msgbox("Error: " + data.error);
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ let menuItem = $(`.raid-array[mdx="${devname}"]`);
|
|
|
+ let raidDetails = $(`.raid-details[mdx="${devname}"]`);
|
|
|
+ let index = menuItem.attr("idx");
|
|
|
+ let domEles = renderRAIDPoolDetail(data, index);
|
|
|
+ let currentShownDetailIndex = 0;
|
|
|
+ if ($(`.raid-array.is-active`).length > 0 && $(`.raid-array.is-active`).attr("idx")){
|
|
|
+ currentShownDetailIndex = parseInt($(`.raid-array.is-active`).attr("idx"));
|
|
|
+ }
|
|
|
+ menuItem.replaceWith(domEles[0]);
|
|
|
+ raidDetails.replaceWith(domEles[1]);
|
|
|
+ showRAIDDetails(currentShownDetailIndex);
|
|
|
+ syncProgressTicker();
|
|
|
+ },
|
|
|
+ error: function(xhr, status, error) {
|
|
|
+ console.error('Error updating RAID status:', error);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 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>';
|
|
|
+ } 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>';
|
|
|
+ }else{
|
|
|
+ icon = '<span class="ts-icon is-spinning is-rotate-icon" style="color: var(--ts-positive-500);"></span>';
|
|
|
+ }
|
|
|
+
|
|
|
+ } else if (raid.State.includes('degraded')) {
|
|
|
+ icon = '<span class="ts-icon is-triangle-exclamation-icon" style="color: var(--ts-warning-600);"></span>';
|
|
|
+ } else if (raid.State.includes('fail')) {
|
|
|
+ icon = '<span class="ts-icon is-circle-xmark-icon" style="color: var(--ts-negative-500);"></span>';
|
|
|
+ } else {
|
|
|
+ icon = '<span class="ts-icon is-question-icon" style="color: var(--ts-gray-500);"></span>';
|
|
|
+ }
|
|
|
+
|
|
|
+ // Add a new menu item for each RAID array
|
|
|
+ const menuItem = `
|
|
|
+ <a class="raid-array item ${index==0?'is-active':''}" idx="${index}" id="raid_menu_${index}" mdx="${mdX}" onclick="showRAIDDetails(${index})">
|
|
|
+ ${icon}
|
|
|
+ <div class="ts-content is-dense">
|
|
|
+ <div>
|
|
|
+ <span class="ts-text is-heavy">${raid.DevicePath}</span> | ${raid.RaidLevel.toUpperCase()}
|
|
|
+ </div>
|
|
|
+ <div class="ts-text is-tiny has-top-spaced-small">
|
|
|
+ ${raid.Name}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </a>
|
|
|
+ `;
|
|
|
+
|
|
|
+ // Add a hidden div for each RAID array's details
|
|
|
+ const raidDetails = `
|
|
|
+ <div id="raid_details_${index}" mdx="${mdX}" idx="${index}" class="raid-details" style="display: none ;">
|
|
|
+ <div class="ts-box">
|
|
|
+ <div class="ts-content is-padded">
|
|
|
+ <div class="ts-header is-start-icon">
|
|
|
+ ${icon}
|
|
|
+ ${raid.DevicePath} | ${raid.RaidLevel.toUpperCase()}
|
|
|
+ </div>
|
|
|
+ <div class="ts-text is-description">
|
|
|
+ ${raid.UUID}<br>
|
|
|
+ ${raid.Name}
|
|
|
+ </div>
|
|
|
+ <div class="ts-text">
|
|
|
+ <span i18n> State
|
|
|
+ // 狀態
|
|
|
+ </span>: ${raid.State}<br>
|
|
|
+ <!-- For Sync progress -->
|
|
|
+ ${isSyncing?getRAIDSyncElement(raid, isSyncing):``}
|
|
|
+ <!-- For RAID Completed -->
|
|
|
+ ${isResyncPending? getRAIDResumeResyncElement(raid):``}
|
|
|
+ <span i18n> Array Size
|
|
|
+ // 陣列大小
|
|
|
+ </span>: ${bytesToHumanReadable(raid.ArraySize * 1024)}<br>
|
|
|
+ <span i18n> Created
|
|
|
+ // 建立時間
|
|
|
+ </span>: <span>${new Date(raid.CreationTime).toLocaleString('en-US', { timeZone: 'UTC' })}</span><br>
|
|
|
+ </div>
|
|
|
+ <table class="ts-table is-single-line has-top-spaced-large">
|
|
|
+ <thead>
|
|
|
+ <tr>
|
|
|
+ <th i18n>Disk Status
|
|
|
+ // 磁碟狀態
|
|
|
+ </th>
|
|
|
+ <th i18n>Counts
|
|
|
+ // 數量
|
|
|
+ </th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody>
|
|
|
+ <tr>
|
|
|
+ <td i18n> Active Devices
|
|
|
+ // 啟用的磁碟
|
|
|
+ </td>
|
|
|
+ <td>${raid.ActiveDevices}</td>
|
|
|
+ </tr>
|
|
|
+ <tr>
|
|
|
+ <td i18n> Working Devices
|
|
|
+ // 工作中的磁碟
|
|
|
+ </td>
|
|
|
+ <td>${raid.WorkingDevices}</td>
|
|
|
+ </tr>
|
|
|
+ <tr>
|
|
|
+ <td i18n> Failed Devices
|
|
|
+ // 故障的磁碟
|
|
|
+ </td>
|
|
|
+ <td>${raid.FailedDevices}</td>
|
|
|
+ </tr>
|
|
|
+ <tr>
|
|
|
+ <td i18n> Spare Devices
|
|
|
+ // 備用磁碟
|
|
|
+ </td>
|
|
|
+ <td>${raid.SpareDevices}</td>
|
|
|
+ </tr>
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="ts-box is-padded has-top-spaced-small">
|
|
|
+ <div class="ts-content">
|
|
|
+
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ return [menuItem, raidDetails];
|
|
|
+ }
|
|
|
+
|
|
|
+ function getRAIDSyncElement(raid, isSyncing=true){
|
|
|
+ return `<div class="sync-progress has-top-spaced-small ${isSyncing?'need-update-raid-sync-progress':''}" devname="${raid.DevicePath}" style="display: ${isSyncing?"auto":"none"};">
|
|
|
+ <div class="ts-progress is-processing">
|
|
|
+ <div class="bar" style="--value: 0">
|
|
|
+ <div class="text">0%</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="ts-text is-description has-top-spaced-small">
|
|
|
+ <span i18n> Synchronized
|
|
|
+ // 已處理</span>
|
|
|
+ <span class="processed_blocks"></span>
|
|
|
+ <span>/</span>
|
|
|
+ <span class="total_blocks"></span>
|
|
|
+ <span i18n> blocks
|
|
|
+ // 個區塊
|
|
|
+ </span><br>
|
|
|
+ <!-- <span i18n> Speed
|
|
|
+ // 速度
|
|
|
+ </span>: <span class="speed"></span><br>
|
|
|
+ <span i18n> Expected Time
|
|
|
+ // 預估時間
|
|
|
+ </span>: <span class="expected_time"></span>
|
|
|
+ -->
|
|
|
+ </div>
|
|
|
+ </div>`;
|
|
|
+ }
|
|
|
+
|
|
|
+ // DOM element for RAID resume resync
|
|
|
+ function getRAIDResumeResyncElement(raid){
|
|
|
+ return `<div class="ts-notice has-top-spaced-small has-bottom-spaced-small">
|
|
|
+ <div class="title">
|
|
|
+ <span i18n> RAID Resync Pending
|
|
|
+ // RAID 重組待處理
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="content">
|
|
|
+ <span i18n> The previous resync operation was interrupted. Click to resume.
|
|
|
+ // 先前的重組操作已中斷,點擊以繼續。
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <button id="activate_raid_btn" onclick="activateSyncPendingDisk('${raid.DevicePath}');" class="ts-button is-fluid has-bottom-spaced-small" i18n> Start Resync
|
|
|
+ // 開始重組
|
|
|
+ </button>`
|
|
|
+ }
|
|
|
+
|
|
|
+ // Function to activate a finished RAID sync
|
|
|
+ // Will set the RAID device to -readwrite state
|
|
|
+ function activateSyncPendingDisk(devname){
|
|
|
+ $.cjax({
|
|
|
+ url: './api/raid/start-resync',
|
|
|
+ method: 'POST',
|
|
|
+ data: { dev: devname},
|
|
|
+ success: function(data) {
|
|
|
+ if (data.error != undefined){
|
|
|
+ // Handle error response
|
|
|
+ console.error('Error start resyncing RAID device:', data.error);
|
|
|
+ }else{
|
|
|
+ // Successfully activated the device
|
|
|
+ console.log('RAID device resync started successfully:', data);
|
|
|
+ msgbox(i18nc("raid_resync_started_succ"));
|
|
|
+ setTimeout(function() {
|
|
|
+ // Refresh the RAID device list after a short delay
|
|
|
+ updateRAIDArrayStatus(devname);
|
|
|
+ }, 300);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
//Create a ticker to check for RAID sync progress
|
|
|
function syncProgressTicker(){
|
|
|
let syncProgressTracker = $(".need-update-raid-sync-progress");
|
|
@@ -213,10 +337,9 @@
|
|
|
data: { devname: devname },
|
|
|
success: function(data) {
|
|
|
if (data.error != undefined){
|
|
|
- // Handle error response
|
|
|
- console.error('Error fetching RAID sync progress:', data.error);
|
|
|
- //Refresh the RAID list
|
|
|
- initRAIDDeviceList();
|
|
|
+ // The device is no longer in sync state. Hide the sync progress bar
|
|
|
+ $(`.sync-progress[devname="${devname}"]`).hide();
|
|
|
+ $(`.sync-progress[devname="${devname}"]`).removeClass("need-update-raid-sync-progress");
|
|
|
}else{
|
|
|
let progress = parseFloat(data.ResyncPercent);
|
|
|
let total_blocks = parseInt(data.TotalBlocks);
|