raid.html 33 KB

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