raid.html 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664
  1. <style>
  2. #activate_raid_btn{
  3. background: var(--ts-positive-400) !important;
  4. border: 0px solid transparent !important;
  5. }
  6. .raid-details{
  7. position: relative;
  8. }
  9. .raid-array-opr-btns{
  10. position: absolute !important;
  11. top: 1em;
  12. right: 1em;
  13. }
  14. .raid-menu-badge{
  15. margin-top: -3px !important;
  16. margin-left: 0.15em !important;
  17. }
  18. .new_raid_disk_selected{
  19. position: absolute;
  20. top: 0;
  21. right: 0.4em;
  22. color: var(--ts-positive-500);
  23. display: none;
  24. }
  25. .new-raid-disk-info.selected .new_raid_disk_selected{
  26. display: block;
  27. }
  28. .new_raid_disk_selected span.ts-icon{
  29. font-size: 2em !important;
  30. }
  31. </style>
  32. <div class="ts-content">
  33. <div class="ts-container is-padded">
  34. <div class="ts-grid mobile:is-stacked">
  35. <div class="column is-6-wide">
  36. <div id="raid_array_list" class="ts-menu is-start-icon is-separated">
  37. <div class="ts-blankslate" style="pointer-events: none; user-select: none; opacity: 0.7;">
  38. <div class="description">
  39. <span class="ts-icon is-circle-notch-icon is-spinning"></span>
  40. <span class="has-start-spaced-small">Loading...</span>
  41. </div>
  42. </div>
  43. </div>
  44. <div class="ts-divider has-top-spaced-small"></div>
  45. <div class="ts-content is-center-aligned">
  46. <button class="ts-button is-start-icon is-positive is-circular">
  47. <span class="ts-icon is-circle-plus-icon" style="color: var(--ts-positive-500);"></span>
  48. <span i18n>Create RAID
  49. // 新增陣列
  50. </span>
  51. </button>
  52. <button onclick="showForceAssembleWarning();" class="ts-button is-start-icon is-positive is-circular">
  53. <span class="ts-icon is-rotate-icon" style="color: var(--ts-primary-500);"></span>
  54. <span i18n>Assemble
  55. // 重組陣列
  56. </span>
  57. </button>
  58. </div>
  59. </div>
  60. <div class="column is-fluid">
  61. <div id="raid_details">
  62. </div>
  63. </div>
  64. </div>
  65. </div>
  66. <style>
  67. </style>
  68. <dialog id="raid_assemble_warning" class="ts-modal">
  69. <div class="content">
  70. <div class="ts-content">
  71. <div class="ts-header" i18n>Confirm force stop & reload RAID from mdadm.conf?
  72. // 確認強制停止並從 mdadm.conf 重新載入 RAID 陣列?
  73. </div>
  74. </div>
  75. <div class="ts-divider"></div>
  76. <div class="ts-content">
  77. <div class="ts-text is-description">
  78. <span i18n> This will stop all RAID arrays and reload all configs from mdadm.conf. Mounted RAID partitions will not be unmounted.
  79. // 這將停止所有 RAID 陣列並從 mdadm.conf 重新載入設定,已掛載的 RAID 分割區不會被卸載。
  80. </span>
  81. </div>
  82. </div>
  83. <div class="ts-divider"></div>
  84. <div class="ts-content is-tertiary">
  85. <div class="ts-wrap is-end-aligned">
  86. <button class="ts-button is-negative" onclick="confirmApplyReassemble();" i18n>Confirm
  87. // 確認
  88. </button>
  89. <button class="ts-button" onclick="cancelForceAssemble();" i18n>Cancel
  90. // 取消
  91. </button>
  92. </div>
  93. </div>
  94. </div>
  95. </dialog>
  96. <dialog id="raid_new" class="ts-modal is-big mobile:is-fullscreen" open>
  97. </dialog>
  98. </div>
  99. <script>
  100. $("#raid_new").load("./components/raid_new.html", function(){
  101. });
  102. function initRAIDDeviceList(){
  103. $.ajax({
  104. url: './api/raid/list',
  105. type: 'GET',
  106. dataType: 'json',
  107. success: function(data) {
  108. $('#raid_array_list').html("");
  109. if (data.error != undefined){
  110. // Handle error response
  111. console.error('Error fetching RAID devices:', data.error);
  112. $('#raid_array_list').append('<div class="ts-text is-error">Error: ' + data.error + '</div>');
  113. }else{
  114. data.forEach((raid, index) => {
  115. let raidDetails = renderRAIDPoolDetail(raid, index);
  116. $("#raid_array_list").append(raidDetails[0]);
  117. $('#raid_details').append(raidDetails[1]);
  118. getRAIDSpaceInfoForDev(raid.DevicePath);
  119. });
  120. if (data.length == 0){
  121. $('#raid_array_list').append(`
  122. <div class="ts-blankslate" style="pointer-events: none; user-select: none; opacity: 0.7;">
  123. <div class="description">
  124. <span class="ts-icon is-circle-check-icon" style="color: var(--ts-positive-400);"></span>
  125. <span class="has-start-spaced-small" i18n> No RAID array found.
  126. // 沒有 RAID 陣列
  127. </span>
  128. </div>
  129. </div>`);
  130. }
  131. }
  132. // Show the first RAID details by default
  133. if (data.length > 0) {
  134. showRAIDDetails(0);
  135. }
  136. relocale(); // Recalculate layout
  137. syncProgressTicker(); // Start the sync progress ticker
  138. },
  139. error: function(xhr, status, error) {
  140. console.error('Error fetching RAID devices:', error);
  141. }
  142. });
  143. }
  144. initRAIDDeviceList();
  145. function refreshRAIDArrayStatus(devname){
  146. //Hide the raid details
  147. $(`.raid-details[mdx=${devname}]`).hide();
  148. updateRAIDArrayStatus(devname, function(data){
  149. if (data.error == undefined){
  150. msgbox(i18nc("raid_device_updated_succ"));
  151. }
  152. $(`.raid-details[mdx=${devname}]`).show();
  153. getRAIDSpaceInfoForDev(devname);
  154. });
  155. }
  156. function updateRAIDArrayStatus(devname, callback=undefined){
  157. if (devname.startsWith('/dev/')) {
  158. devname = devname.slice(5);
  159. }
  160. $.ajax({
  161. url: './api/raid/info?dev=' + devname,
  162. type: 'GET',
  163. dataType: 'json',
  164. success: function(data) {
  165. if (data.error != undefined){
  166. // Handle error response
  167. console.error('Error fetching RAID status:', data.error);
  168. msgbox("Error: " + data.error);
  169. if (callback){
  170. callback(data);
  171. }
  172. return
  173. }
  174. // Update the RAID array status
  175. // Find the corresponding menu item and details tab
  176. let menuItem = $(`.raid-array[mdx="${devname}"]`);
  177. let raidDetails = $(`.raid-details[mdx="${devname}"]`);
  178. let index = menuItem.attr("idx");
  179. let domEles = renderRAIDPoolDetail(data, index);
  180. let currentShownDetailIndex = 0;
  181. if ($(`.raid-array.is-active`).length > 0 && $(`.raid-array.is-active`).attr("idx")){
  182. currentShownDetailIndex = parseInt($(`.raid-array.is-active`).attr("idx"));
  183. }
  184. menuItem.replaceWith(domEles[0]);
  185. raidDetails.replaceWith(domEles[1]);
  186. showRAIDDetails(currentShownDetailIndex);
  187. syncProgressTicker();
  188. if (callback){
  189. callback(data);
  190. }
  191. },
  192. error: function(xhr, status, error) {
  193. console.error('Error updating RAID status:', error);
  194. }
  195. });
  196. }
  197. function deleteRAIDArray(devname){
  198. if (devname.startsWith('/dev/')) {
  199. devname = devname.slice(5);
  200. }
  201. /*
  202. //TODO: API still not ready
  203. $.cjax({
  204. url: './api/raid/delete',
  205. method: 'POST',
  206. data: { dev: devname},
  207. success: function(data) {
  208. if (data.error != undefined){
  209. // Handle error response
  210. console.error('Error deleting RAID device:', data.error);
  211. msgbox("Error: " + data.error);
  212. }else{
  213. // Successfully deleted the device
  214. console.log('RAID device deleted successfully:', data);
  215. msgbox(i18nc("raid_device_deleted_succ"));
  216. setTimeout(function() {
  217. // Refresh the RAID device list after a short delay
  218. initRAIDDeviceList();
  219. }, 300);
  220. }
  221. },
  222. });
  223. */
  224. }
  225. // Utility function to convert bytes to human-readable format
  226. function bytesToHumanReadable(bytes) {
  227. const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
  228. if (bytes === 0) return '0 B';
  229. const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
  230. return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + sizes[i];
  231. }
  232. function getRAIDSpaceInfoForDev(devname){
  233. if (devname.startsWith('/dev/')) {
  234. devname = devname.slice(5);
  235. }
  236. let gaugeElement = $(`.raid-details[mdx=${devname}]`).find(".raid-usage-info");
  237. let updateElement = $(`.raid-details[mdx=${devname}]`).find(".raid-total-used-space");
  238. $.get("./api/raid/overview", function(data){
  239. if (data.error != undefined){
  240. // Handle error response
  241. return;
  242. }
  243. if (data && Array.isArray(data)) {
  244. let raidInfo = data.find(raid => raid.Name === devname);
  245. if (raidInfo) {
  246. let usedPercentage = (raidInfo.UsedSize / raidInfo.TotalSize) * 100;
  247. gaugeElement.find('.bar').css('--value', usedPercentage.toFixed(1));
  248. gaugeElement.find('.bar .text').text(`${usedPercentage.toFixed(1)}%`);
  249. updateElement.html(bytesToHumanReadable(raidInfo.UsedSize));
  250. }
  251. }
  252. })
  253. }
  254. // Function to render RAID pool details
  255. // This function creates the HTML structure for each RAID pool
  256. // return the DOM element for the side menu and detail tab
  257. function renderRAIDPoolDetail(raid, index){
  258. // Add a new menu item for each RAID array
  259. let mdX = raid.DevicePath.split('/').pop();
  260. let isSyncing = false;
  261. let isResyncPending = false;
  262. let icon = '';
  263. if (raid.State.includes('clean') && !raid.State.includes('sync')) {
  264. icon = '<span class="ts-icon is-check-icon" style="color: var(--ts-positive-500); font-size: 2em;"></span>';
  265. } else if (raid.State.includes('sync')) {
  266. isSyncing = true;
  267. if (raid.State.includes('resyncing') && raid.State.includes('PENDING')) {
  268. //Syncing is pending
  269. isResyncPending = true;
  270. icon = '<span class="ts-icon is-rotate-icon" style="color: var(--ts-positive-500); font-size: 2em;"></span>';
  271. }else{
  272. icon = '<span class="ts-icon is-spinning is-rotate-icon" style="color: var(--ts-positive-500); font-size: 2em;"></span>';
  273. }
  274. } else if (raid.State.includes('degraded')) {
  275. icon = '<span class="ts-icon is-triangle-exclamation-icon" style="color: var(--ts-warning-600); font-size: 2em;"></span>';
  276. } else if (raid.State.includes('fail')) {
  277. icon = '<span class="ts-icon is-circle-xmark-icon" style="color: var(--ts-negative-500); font-size: 2em;"></span>';
  278. } else {
  279. icon = '<span class="ts-icon is-question-icon" style="color: var(--ts-gray-500); font-size: 2em;"></span>';
  280. }
  281. // Add a new menu item for each RAID array
  282. const menuItem = `
  283. <a class="raid-array item ${index==0?'is-active':''}" idx="${index}" id="raid_menu_${index}" mdx="${mdX}" onclick="showRAIDDetails(${index})">
  284. ${icon}
  285. <div class="ts-content is-dense">
  286. <div>
  287. <span class="ts-text is-heavy">${raid.DevicePath}</span>
  288. <span class="ts-badge is-secondary raid-menu-badge">${raid.RaidLevel.toUpperCase()}</span>
  289. </div>
  290. <div class="ts-text is-tiny has-top-spaced-small">
  291. ${raid.Name}
  292. </div>
  293. </div>
  294. </a>
  295. `;
  296. // Add a hidden div for each RAID array's details
  297. const raidDetails = `
  298. <div id="raid_details_${index}" mdx="${mdX}" idx="${index}" class="raid-details" style="display: none ;">
  299. <div class="ts-box">
  300. <div class="ts-content is-padded">
  301. <div class="ts-header is-start-icon">
  302. ${icon}
  303. ${raid.DevicePath} <span class="ts-badge is-start-spaced">${raid.RaidLevel.toUpperCase()}</span>
  304. </div>
  305. <div class="ts-text is-description">
  306. ${raid.UUID}<br>
  307. ${raid.Name}
  308. </div>
  309. <div class="ts-text">
  310. <span i18n> State
  311. // 狀態
  312. </span>: ${raid.State}<br>
  313. <!-- For Sync progress -->
  314. ${isSyncing?getRAIDSyncElement(raid, isSyncing):``}
  315. <!-- For RAID Completed -->
  316. ${isResyncPending? getRAIDResumeResyncElement(raid):``}
  317. <span i18n> Array Size
  318. // 陣列大小
  319. </span>: ${bytesToHumanReadable(raid.ArraySize * 1024)}<br>
  320. <span i18n> Created
  321. // 建立時間
  322. </span>: <span>${new Date(raid.CreationTime).toLocaleString('en-US', { timeZone: 'UTC' })}</span><br>
  323. </div>
  324. <!-- Disk States Summary -->
  325. <table class="ts-table is-single-line has-top-spaced-large">
  326. <thead>
  327. <tr>
  328. <th i18n>Disk Status
  329. // 磁碟狀態
  330. </th>
  331. <th i18n>Counts
  332. // 數量
  333. </th>
  334. </tr>
  335. </thead>
  336. <tbody>
  337. <tr>
  338. <td i18n> Active Devices
  339. // 啟用的磁碟
  340. </td>
  341. <td>${raid.ActiveDevices}</td>
  342. </tr>
  343. <tr>
  344. <td i18n> Working Devices
  345. // 工作中的磁碟
  346. </td>
  347. <td>${raid.WorkingDevices}</td>
  348. </tr>
  349. <tr>
  350. <td i18n> Failed Devices
  351. // 故障的磁碟
  352. </td>
  353. <td>${raid.FailedDevices}</td>
  354. </tr>
  355. <tr>
  356. <td i18n> Spare Devices
  357. // 備用磁碟
  358. </td>
  359. <td>${raid.SpareDevices}</td>
  360. </tr>
  361. </tbody>
  362. </table>
  363. <!-- Usage Counters -->
  364. <div class="ts-grid is-evenly-divided has-top-spaced-large">
  365. <div class="column">
  366. <div class="ts-wrap is-middle-aligned">
  367. <div class="ts-gauge is-small is-circular raid-usage-info">
  368. <div class="bar" style="--value: 0">
  369. <div class="text">0%</div>
  370. </div>
  371. </div>
  372. <div>
  373. <div class="ts-text is-bold" i18n> Used Space
  374. // 已使用空間
  375. </div>
  376. <span class="raid-total-used-space">???</span> / ${bytesToHumanReadable(raid.ArraySize * 1024)}
  377. </div>
  378. </div>
  379. </div>
  380. </div>
  381. </div>
  382. </div>
  383. <!-- Child Disks -->
  384. <div class="has-top-spaced-small">
  385. ${getRAIDChildDiskElement(raid.DeviceInfo)}
  386. </div>
  387. <!-- Operations -->
  388. <div class="has-top-spaced-small">
  389. <div class="ts-box">
  390. <div class="ts-content">
  391. <button class="ts-button is-start-icon is-positive is-circular" onclick="" >
  392. <span class="ts-icon is-rotate-icon" style="color: var(--ts-primary-500);"></span>
  393. <span i18n> Assemble
  394. // 重組陣列
  395. </span>
  396. </button>
  397. <button class="ts-button is-circular is-start-icon is-negative">
  398. <span class="ts-icon is-trash-icon"></span>
  399. <span i18n> Delete RAID
  400. // 刪除陣列
  401. </span>
  402. </button>
  403. </div>
  404. </div>
  405. </div>
  406. <!-- Operations -->
  407. <div class="raid-array-opr-btns">
  408. <div class="ts-content">
  409. <button class="ts-button is-circular is-icon" onclick="refreshRAIDArrayStatus('${mdX}');">
  410. <span class="ts-icon is-arrows-rotate-icon"></span>
  411. </button>
  412. </div>
  413. </div>
  414. </div>
  415. `;
  416. return [menuItem, raidDetails];
  417. }
  418. function getRAIDSyncElement(raid, isSyncing=true){
  419. return `<div class="sync-progress has-top-spaced-small ${isSyncing?'need-update-raid-sync-progress':''}" devname="${raid.DevicePath}" style="display: ${isSyncing?"auto":"none"};">
  420. <div class="ts-progress is-processing">
  421. <div class="bar" style="--value: 0">
  422. <div class="text">0%</div>
  423. </div>
  424. </div>
  425. <div class="ts-text is-description has-top-spaced-small">
  426. <span i18n> Synchronized
  427. // 已處理</span>
  428. <span class="processed_blocks"></span>
  429. <span>/</span>
  430. <span class="total_blocks"></span>
  431. <span i18n> blocks
  432. // 個區塊
  433. </span><br>
  434. <!-- <span i18n> Speed
  435. // 速度
  436. </span>: <span class="speed"></span><br>
  437. <span i18n> Expected Time
  438. // 預估時間
  439. </span>: <span class="expected_time"></span>
  440. -->
  441. </div>
  442. </div>`;
  443. }
  444. // DOM element for RAID resume resync
  445. function getRAIDResumeResyncElement(raid){
  446. return `<div class="ts-notice has-top-spaced-small has-bottom-spaced-small">
  447. <div class="title">
  448. <span i18n> RAID Resync Pending
  449. // RAID 重組待處理
  450. </span>
  451. </div>
  452. <div class="content">
  453. <span i18n> The previous resync operation was interrupted. Click to resume.
  454. // 先前的重組操作已中斷,點擊以繼續。
  455. </span>
  456. </div>
  457. </div>
  458. <button id="activate_raid_btn" onclick="activateSyncPendingDisk('${raid.DevicePath}');" class="ts-button is-fluid has-bottom-spaced-small" i18n> Start Resync
  459. // 開始重組
  460. </button>`
  461. }
  462. // DOM elements for child disks
  463. function getRAIDChildDiskElement(raidDeviceInfo){
  464. if (raidDeviceInfo.length == 0 || raidDeviceInfo == null){
  465. return `<div class="ts-blankslate" style="pointer-events: none; user-select: none; opacity: 0.7;">
  466. <div class="description" i18n>No assigned disks
  467. // 沒有分配的磁碟
  468. </div>
  469. </div>`;
  470. }
  471. //Render each disk
  472. let result = '';
  473. for (let i = 0; i < raidDeviceInfo.length; i++) {
  474. let disk = raidDeviceInfo[i];
  475. if (disk.RaidDevice == -1){
  476. continue;
  477. }
  478. let elementUUID = "raid_child_" + disk.DevicePath.replace(/\//g, "_");
  479. let diskSdx = disk.DevicePath.split('/').pop();
  480. let thisDiskInfo = getDiskInfoDevicePath(diskSdx); //Try to load disk info from cache
  481. if (thisDiskInfo == null){
  482. //Make a request to get the disk info
  483. let thisDiskInfo = null;
  484. $.ajax({
  485. url: `./api/info/disk/${diskSdx}`,
  486. type: 'GET',
  487. dataType: 'json',
  488. success: function(data) {
  489. if (data.error != undefined){
  490. return;
  491. }
  492. thisDiskInfo = data;
  493. $(`.raid-child-disk[diskid='${elementUUID}']`).find(".raid-disk-name").text(thisDiskInfo.model);
  494. },
  495. error: function(xhr, status, error) {
  496. console.error('Error fetching disk info:', error);
  497. }
  498. });
  499. }
  500. result += `
  501. <div class="ts-box is-padded has-bottom-spaced-small raid-child-disk" diskid="${elementUUID}">
  502. <div class="ts-content">
  503. <div>
  504. <span class="ts-badge is-secondary has-end-spaced-small" style="margin-top: -0.3em;">${disk.DevicePath}</span>
  505. <span class="ts-text is-heavy raid-disk-name">Raid Device ${disk.RaidDevice}</span>
  506. </div>
  507. <div class="ts-text is-tiny has-top-spaced-small">
  508. <div class="has-start-spaced-small">
  509. <span i18n> State
  510. // 狀態
  511. </span>: ${disk.State.join(', ')}
  512. </div>
  513. </div>
  514. </div>
  515. </div>
  516. `;
  517. }
  518. return result;
  519. }
  520. // Function to activate a finished RAID sync
  521. // Will set the RAID device to -readwrite state
  522. function activateSyncPendingDisk(devname){
  523. $.cjax({
  524. url: './api/raid/start-resync',
  525. method: 'POST',
  526. data: { dev: devname},
  527. success: function(data) {
  528. if (data.error != undefined){
  529. // Handle error response
  530. console.error('Error start resyncing RAID device:', data.error);
  531. }else{
  532. // Successfully activated the device
  533. console.log('RAID device resync started successfully:', data);
  534. msgbox(i18nc("raid_resync_started_succ"));
  535. setTimeout(function() {
  536. // Refresh the RAID device list after a short delay
  537. updateRAIDArrayStatus(devname);
  538. }, 300);
  539. }
  540. },
  541. });
  542. }
  543. //Create a ticker to check for RAID sync progress
  544. function syncProgressTicker(){
  545. let syncProgressTracker = $(".need-update-raid-sync-progress");
  546. if (syncProgressTracker.length > 0){
  547. syncProgressTracker.each(function(){
  548. let devname = $(this).attr("devname");
  549. $.ajax({
  550. url: './api/raid/sync?dev=' + devname,
  551. type: 'GET',
  552. dataType: 'json',
  553. data: { devname: devname },
  554. success: function(data) {
  555. if (data.error != undefined){
  556. // The device is no longer in sync state. Hide the sync progress bar
  557. $(`.sync-progress[devname="${devname}"]`).hide();
  558. $(`.sync-progress[devname="${devname}"]`).removeClass("need-update-raid-sync-progress");
  559. }else{
  560. let progress = parseFloat(data.ResyncPercent);
  561. let total_blocks = parseInt(data.TotalBlocks);
  562. let processed_blocks = parseInt(data.CompletedBlocks);
  563. let expected_time = data.ExpectedTime;
  564. let speed = data.Speed;
  565. $(`.sync-progress[devname="${devname}"] .bar`).css('--value', progress);
  566. $(`.sync-progress[devname="${devname}"] .bar .text`).text(`${progress.toFixed(1)}%`);
  567. $(`.sync-progress[devname="${devname}"] .processed_blocks`).text(processed_blocks);
  568. $(`.sync-progress[devname="${devname}"] .total_blocks`).text(total_blocks);
  569. //$(`.sync-progress[devname="${devname}"] .ts-text.is-description .speed`).text(speed);
  570. //$(`.sync-progress[devname="${devname}"] .ts-text.is-description .expected_time`).text(expected_time);
  571. }
  572. },
  573. error: function(xhr, status, error) {
  574. console.error('Error fetching RAID sync progress:', error);
  575. }
  576. });
  577. });
  578. }
  579. }
  580. setInterval(syncProgressTicker, 5000); // Check every 5 seconds
  581. function showRAIDDetails(index) {
  582. $('.raid-details').hide(); // Hide all RAID details
  583. $(`#raid_details_${index}`).show(); // Show the selected RAID details
  584. $('.raid-array.is-active').removeClass('is-active'); // Remove active class from all menu items
  585. $(`#raid_menu_${index}`).addClass('is-active'); // Add active class to the selected menu item
  586. relocale(); // Recalculate layout
  587. }
  588. /* Assemble RAID */
  589. function cancelForceAssemble(){
  590. $('#raid_assemble_warning')[0].close();
  591. }
  592. function showForceAssembleWarning(){
  593. $('#raid_assemble_warning')[0].showModal();
  594. }
  595. function AssembleAllRAID(){
  596. $.cjax({
  597. url: './api/raid/reassemble',
  598. method: 'POST',
  599. success: function(data) {
  600. if (data.error != undefined){
  601. // Handle error response
  602. console.error('Error reassembling RAID device:', data.error);
  603. }else{
  604. // Successfully activated the device
  605. console.log('RAID device reassemble started successfully:', data);
  606. msgbox(i18nc("raid_reassemble_started_succ"));
  607. setTimeout(function() {
  608. // Refresh the RAID device list after a short delay
  609. initRAIDDeviceList();
  610. }, 300);
  611. }
  612. },
  613. });
  614. }
  615. function confirmApplyReassemble(){
  616. $('#raid_assemble_warning')[0].close();
  617. AssembleAllRAID();
  618. }
  619. </script>