index.html 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>RAID</title>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
  7. <link rel="stylesheet" href="../../../script/semantic/semantic.min.css">
  8. <script type="text/javascript" src="../../../script/jquery.min.js"></script>
  9. <script type="text/javascript" src="../../../script/semantic/semantic.min.js"></script>
  10. <style>
  11. .mdevice{
  12. color: rgb(136, 136, 136);
  13. font-weight: lighter;
  14. }
  15. .mdevuuid{
  16. font-size: 0.6em !important;
  17. }
  18. .volinfo{
  19. text-align: left !important;
  20. margin-top: 0.6em !important;
  21. }
  22. .volinfo .numeric{
  23. font-weight: lighter;
  24. color: #0d79ea;
  25. }
  26. .volinfo .percentage{
  27. font-weight: lighter;
  28. color: rgb(102, 102, 102);
  29. position: absolute;
  30. text-align: right;
  31. right: 1em;
  32. top: 0em;
  33. }
  34. .raidVol .usedvol{
  35. margin-bottom: 2em !important;
  36. }
  37. .raidVol .usedvol .bar{
  38. background-color: #0d79ea !important;
  39. }
  40. .raidVol{
  41. cursor: pointer;
  42. }
  43. .raidVol:hover{
  44. background-color: rgb(240, 240, 240);
  45. }
  46. .raidVol.active{
  47. background-color: rgb(240, 240, 240);
  48. }
  49. .healthIcon{
  50. position: absolute;
  51. left: 35px;
  52. top: 38px;
  53. border-radius: 50% !important;
  54. background-color: white;
  55. }
  56. .healthIcon i {
  57. margin-right: 0 !important;
  58. }
  59. /* RAID Option Button */
  60. .raidOptionBtn{
  61. position: absolute;
  62. top: 1em;
  63. right: 1em;
  64. }
  65. /* Statistics of Device Status */
  66. .deviceOverview{
  67. background-color: #f3f3f3 !important;
  68. border-radius: 0.4em !important;
  69. }
  70. .deviceOverview .ui.statistics .statistic .label{
  71. text-transform: none !important;
  72. font-weight: 300;
  73. }
  74. /* RAID device members */
  75. .raiddevice{
  76. border-radius: 0.4em !important;
  77. margin-bottom: 0 !important;
  78. margin-top: 0 !important;
  79. position: relative;
  80. }
  81. .raiddevice:hover{
  82. background-color: #f3f3f3 !important;
  83. }
  84. .raiddevice .capinfo{
  85. position: absolute;
  86. color:rgb(136, 136, 136);
  87. right: 1em;
  88. top: 1em;
  89. text-align: right;
  90. }
  91. .raiddevice .capinfo .readonlytag{
  92. background-color: #cc3d3a;
  93. color:white;
  94. padding: 2px;
  95. padding-left: 5px;
  96. padding-right: 5px;
  97. }
  98. /* RAID memeber options */
  99. .raidDeviceOptions{
  100. width: 100%;
  101. min-height: 2em;
  102. }
  103. .raidDeviceOptions .content{
  104. float :right;
  105. }
  106. .notMountedLabel{
  107. color:rgb(194, 194, 194);
  108. font-weight: 300;
  109. }
  110. /* Danger zone css */
  111. .dangerzone{
  112. border: 1px solid #dedede;
  113. border-radius: 0.6em;
  114. overflow: hidden;
  115. padding-bottom: 1em;
  116. }
  117. .dangerzone .header{
  118. color:white;
  119. padding: 1em;
  120. background-color: #cc3d3a;
  121. margin-bottom: 1em;
  122. font-weight: bolder;
  123. }
  124. .dangerzone summary{
  125. cursor: pointer;
  126. user-select: none;
  127. }
  128. </style>
  129. </head>
  130. <body>
  131. <br>
  132. <div class="ui container">
  133. <!--
  134. <div class="ui basic segment">
  135. <h3 class="ui header">
  136. <span id="RAIDhealthyState"></span>
  137. <div class="sub header"><span id="RAIDvolCount">0</span> RAID Storage Volume Detected</div>
  138. </h3>
  139. </div>
  140. -->
  141. <div class="ui stackable grid">
  142. <div class="six wide column" style="border-right: 1px solid #e0e0e0;">
  143. <div id="raidVolList">
  144. <p style="text-align: center;"><i class="ui loading spinner icon"></i></p>
  145. </div>
  146. <div class="ui divider"></div>
  147. <div style="width: 100%;" align="center">
  148. <button title="Add new RAID volume" onclick="addNewRaidVolume()" class="circular basic green large ui icon button">
  149. <i class="green add icon"></i>
  150. </button>
  151. <button title="Assemble RAID Pools" onclick="assembleRaidVolumes()" class="circular basic orange large ui button">
  152. <i class="orange refresh icon"></i> Assemble
  153. </button>
  154. </div>
  155. </div>
  156. <div class="ten wide column">
  157. <div id="volumeDetail">
  158. <div id="raidDiskOverview">
  159. <h3 class="ui header">
  160. <i id="RAIDOverviewStateIcon" class="ui green check circle icon"></i>
  161. <div class="content">
  162. <span id="RAIDOverviewDiskpath">Loading device details</span>
  163. <div class="sub header">
  164. <span id="RAIDOverviewDetails"></span><br>
  165. <small style="font-weight: 300;" id="RAIDUUID"></small>
  166. </div>
  167. </div>
  168. </h3>
  169. <div class="ui divider"></div>
  170. <div class="ui basic segment deviceOverview">
  171. <div class="ui tiny four statistics">
  172. <div class="statistic">
  173. <div class="value">
  174. <img src="../disk/raid/img/drive.svg" class="ui inline image">
  175. <span id="RAIDActiveDevices">0</span>
  176. </div>
  177. <div class="label">
  178. Active Devices
  179. </div>
  180. </div>
  181. <div class="statistic">
  182. <div class="value">
  183. <img src="../disk/raid/img/drive-working.svg" class="ui inline image">
  184. <span id="RAIDWorkingDevices">0</span>
  185. </div>
  186. <div class="label">
  187. Working Devices
  188. </div>
  189. </div>
  190. <div class="statistic">
  191. <div class="value">
  192. <img src="../disk/raid/img/drive-failed.svg" class="ui inline image">
  193. <span id="RAIDFailedDevices">0</span>
  194. </div>
  195. <div class="label">
  196. Failed Devices
  197. </div>
  198. </div>
  199. <div class="statistic">
  200. <div class="value">
  201. <img src="../disk/raid/img/drive-spare.svg" class="ui inline image">
  202. <span id="RAIDSpareDevices">0</span>
  203. </div>
  204. <div class="label">
  205. Spare Devices
  206. </div>
  207. </div>
  208. </div>
  209. </div>
  210. <div class="raidOptionBtn">
  211. <button onclick="reloadRAIDVolDetail();" class="ui basic circular icon button"><i class="ui green refresh icon"></i></button>
  212. </div>
  213. <div>
  214. <button onclick="addDiskToArray();" title="Add new disk to this volume" class="circular small basic ui button">
  215. <i class="green add icon"></i> Add Disk
  216. </button>
  217. <button onclick="expandRAIDArray();" title="Expand volume to fit disks capacity" class="circular small basic ui button">
  218. <i class="purple expand icon"></i> Expand Volume
  219. </button>
  220. </div>
  221. </div>
  222. <div class="ui divider"></div>
  223. <div id="raidDiskList">
  224. </div>
  225. <div class="ui message" style="display:none;" id="raidDataLossWarning">
  226. <i class="yellow exclamation triangle icon"></i> Minimum disk number reached. "Remove Disk" function is disabled to prevent data loss.<br>
  227. </div>
  228. <div class="ui divider"></div>
  229. <div style="width: 100%; text-align: center; margin-bottom: 1em;">
  230. <p><i class="yellow exclamation triangle icon"></i> Danger Zone <i class="yellow exclamation triangle icon"></i></p>
  231. </div>
  232. <div style="width: 100%;" align="center">
  233. <button onclick="RAIDRemove();" class="circular red ui button">
  234. <i class="trash icon"></i> Remove RAID
  235. </button>
  236. </div>
  237. <br><br><br><br>
  238. </div>
  239. <div id="noRAIDVolumeWarning" style="display:none;">
  240. <div class="ui basic segment">
  241. <h4 class="ui header">
  242. <img src="../disk/raid/img/drive-notfound.svg">
  243. <div class="content">
  244. <span>No RAID Volume</span>
  245. <div class="sub header">Create a new RAID volume by pressing the <i class="ui green circle add icon" style="margin-right: 0;"></i> button</div>
  246. </div>
  247. </h4>
  248. </div>
  249. </div>
  250. </div>
  251. </div>
  252. <!-- Disk Remove Confirmation -->
  253. <div id="confirmDiskRemove" class="ui mini modal">
  254. <i class="close icon"></i>
  255. <div class="header">
  256. Confirm Disk Remove
  257. </div>
  258. <div class="image content">
  259. <div class="ui small image">
  260. <img src="../disk/raid/img/remove-warning.svg">
  261. </div>
  262. <div class="description">
  263. <p id="oprconfirm"></p>
  264. <p><b>Removing a disk from RAID volume might risk data loss.</b> Make sure you know what you are doing.<br></p>
  265. </div>
  266. </div>
  267. <div class="actions">
  268. <div class="ui black deny button">
  269. Cancel
  270. </div>
  271. <div class="ui negative left labeled icon button" onclick="confirmRemoveDisk();">
  272. <i class="trash icon"></i>
  273. REMOVE
  274. </div>
  275. </div>
  276. </div>
  277. <!-- RAID Remove Confirmation -->
  278. <div id="confirmRAIDRemove" class="ui small modal">
  279. <i class="close icon"></i>
  280. <div class="header">
  281. Confirm RAID Volume Remove
  282. </div>
  283. <div class="image content">
  284. <div class="ui small image">
  285. <img src="../disk/raid/img/remove-warning.svg">
  286. </div>
  287. <div class="description">
  288. <h4 style="color: rgb(206, 31, 31);"><span id="removingRAIDName"></span></h4>
  289. <p><b>This will clear all stored data in this volume. Please make sure you have backup all important data.</b> Confirm Remove?<br></p>
  290. </div>
  291. </div>
  292. <div class="actions">
  293. <div class="ui black deny button">
  294. Cancel
  295. </div>
  296. <div class="ui negative left labeled icon button" onclick="confirmRAIDRemove();">
  297. <i class="trash icon"></i>
  298. REMOVE
  299. </div>
  300. </div>
  301. </div>
  302. <script>
  303. var raidManager = {
  304. raidDetails: {}, //The raid array details from last server req
  305. editingArray: "",
  306. removePendingDisk: "",
  307. removePendingSourceVol: ""
  308. };
  309. var raidInfoTicker = undefined;
  310. /* Information Update Ticker */
  311. function RAIDInfoUpdateTicker(){
  312. function compareRAIDstate(oldState, newState) {
  313. const fieldsToCompare = [
  314. "Consistency",
  315. "FailedDevices",
  316. "RebuildStatus",
  317. "SpareDevices",
  318. "State",
  319. "TotalDevices",
  320. "WorkingDevices"
  321. ];
  322. for (const field of fieldsToCompare) {
  323. if (oldState[field] !== newState[field]) {
  324. return false;
  325. }
  326. }
  327. return true;
  328. }
  329. if ($("#raidDiskOverview").length > 0){
  330. //Still on the RAID manager page
  331. if (raidManager.editingArray == undefined || raidManager.editingArray == ""){
  332. //Nothing selected or loaded. Skip this round
  333. setTimeout(RAIDInfoUpdateTicker, 10000);
  334. return;
  335. }
  336. $.get("../../system/disk/raid/detail?devName=" + raidManager.editingArray, function(data){
  337. if (data.error != undefined){
  338. //Something went wrong loading this array (removed by another admin?)
  339. }else{
  340. //Validate if both details are identical
  341. if (compareRAIDstate(data,raidManager.raidDetails)){
  342. //No information updated
  343. }else{
  344. //Something updated. Reload the RAID volume info
  345. loadRAIDVolDetail(raidManager.editingArray);
  346. }
  347. }
  348. //Update again later
  349. raidInfoTicker = setTimeout(RAIDInfoUpdateTicker, 5000);
  350. });
  351. }else{
  352. //Release the ticker
  353. console.log("RAID status ticker released");
  354. raidInfoTicker = undefined;
  355. }
  356. }
  357. //Set the ticker
  358. if (raidInfoTicker == undefined){
  359. console.log("RAID status ticker started");
  360. raidInfoTicker = setTimeout(RAIDInfoUpdateTicker, 1000);
  361. }
  362. /* RAID Create Functions */
  363. function addNewRaidVolume(){
  364. //Open newRAID window
  365. parent.newFloatWindow({
  366. url: "SystemAO/disk/raid/newRAID.html#" + Date.now(),
  367. width: 940,
  368. height: 480,
  369. appicon: "SystemAO/disk/raid/img/raid.svg",
  370. title: `New RAID volume`,
  371. parent: ao_module_windowID,
  372. callback: "handleRAIDCreateCallback"
  373. });
  374. }
  375. window.handleRAIDCreateCallback = function(data){
  376. //Done adding raid vol
  377. setTimeout(function(){
  378. reloadRAIDVolDetail();
  379. if (data.error != undefined){
  380. msgbox(capitalize(data.error), false, 5000);
  381. }else{
  382. msgbox("New RAID volume created");
  383. }
  384. }, 300);
  385. }
  386. /* RAID Remove Function */
  387. function RAIDRemove(){
  388. if (raidManager.editingArray == ""){
  389. console.log("RAID manager have no editing array set")
  390. return;
  391. }
  392. $("#removingRAIDName").text(raidManager.editingArray);
  393. $("#confirmRAIDRemove").modal("show");
  394. }
  395. function confirmRAIDRemove(){
  396. var apiObject = {
  397. api: "../system/disk/raid/remove",
  398. data: {
  399. "raidDev": raidManager.editingArray,
  400. },
  401. title: `<i class='yellow exclamation triangle icon'></i> REMOVE RAID VOLUME <i class='yellow exclamation triangle icon'></i>`,
  402. desc: `Confirm format and remove RAID: ${raidManager.editingArray}`,
  403. thisuser: true, //This username as default, set to false for entering other user
  404. method: "POST",
  405. success: undefined
  406. }
  407. apiObject = encodeURIComponent(JSON.stringify(apiObject));
  408. parent.newFloatWindow({
  409. url: "SystemAO/security/authreq.html#" + apiObject,
  410. width: 480,
  411. height: 300,
  412. appicon: "SystemAO/security/img/lock.svg",
  413. title: `Confirm Disk Remove`,
  414. parent: ao_module_windowID,
  415. callback: "handleRAIDRemoveCallback"
  416. });
  417. }
  418. window.handleRAIDRemoveCallback = function(data){
  419. if (data.error != undefined){
  420. //Something went wrong
  421. msgbox(capitalize(data.error), false, 5000);
  422. }else{
  423. setTimeout(function(){
  424. //RAID volume not exist anymore. Reset everything
  425. raidManager = {};
  426. initRAIDVolList();
  427. }, 300);
  428. }
  429. }
  430. /* New Disk Function */
  431. function addDiskToArray(arrayName=raidManager.editingArray){
  432. let payload = {
  433. "md": arrayName,
  434. }
  435. payload = encodeURIComponent(JSON.stringify(payload));
  436. parent.newFloatWindow({
  437. url: "SystemAO/disk/raid/newdisk.html#" + payload,
  438. width: 940,
  439. height: 480,
  440. appicon: "SystemAO/disk/raid/img/raid.svg",
  441. title: `Add new disk to RAID volume ${arrayName}`,
  442. parent: ao_module_windowID,
  443. callback: "handleAddDiskCallback"
  444. });
  445. }
  446. window.handleAddDiskCallback = function(data){
  447. //Done adding disk
  448. setTimeout(function(){
  449. msgbox("New disk added");
  450. reloadRAIDVolDetail();
  451. }, 300);
  452. }
  453. /* RAID Array Expand */
  454. function expandRAIDArray(arrayName=raidManager.editingArray){
  455. $.ajax({
  456. url: "../../system/disk/raid/grow",
  457. method: "POST",
  458. data: {
  459. "raidDev": arrayName,
  460. },
  461. success: function(data){
  462. if (data.error != undefined){
  463. msgbox(capitalize(data.error), false);
  464. }else{
  465. msgbox("mdadm grow operation started");
  466. reloadRAIDVolDetail();
  467. }
  468. }
  469. })
  470. }
  471. /* Disk Remove Functions */
  472. function removeDisk(arrayName, diskPath){
  473. console.log(arrayName, diskPath);
  474. $("#oprconfirm").html("Remove <b>" + diskPath + "</b> from <b>" + arrayName + "</b>");
  475. $("#confirmDiskRemove").modal('show');
  476. raidManager.removePendingDisk = diskPath;
  477. raidManager.removePendingSourceVol = arrayName;
  478. }
  479. function confirmRemoveDisk(){
  480. $.ajax({
  481. url: "../../system/disk/raid/removeMemeber",
  482. data: {
  483. raidDev: raidManager.removePendingSourceVol,
  484. memDev: raidManager.removePendingDisk
  485. },
  486. method: "POST",
  487. success: function(data){
  488. handleRemoveDiskCallback(data);
  489. }
  490. })
  491. }
  492. //Remove disk completed
  493. window.handleRemoveDiskCallback = function(succ){
  494. console.log(data);
  495. raidManager.removePendingDisk = "";
  496. raidManager.removePendingSourceVol = "";
  497. setTimeout(function(){
  498. msgbox("Target disk removed");
  499. reloadRAIDVolDetail();
  500. }, 300);
  501. }
  502. //Quick function to check if the disk is healthy
  503. function isHealthy(stateText) {
  504. // Check if the stateText contains any unhealthy states
  505. if (stateText.includes('faulty') || stateText.includes('resyncing') ||
  506. stateText.includes('recovering') || stateText.includes('degraded') ||
  507. stateText.includes('inactive')) {
  508. return false; // Array is not healthy
  509. } else {
  510. return true; // Array is healthy
  511. }
  512. }
  513. //Get the RAID health state icon from state Text
  514. function getStateIconFromStateText(stateText) {
  515. if (stateText.includes('faulty')) {
  516. return 'ui red times circle icon';
  517. } else if (stateText.includes('resyncing')) {
  518. return 'ui green loading sync icon';
  519. } else if (stateText.includes('recovering')) {
  520. return 'ui yellow loading sync icon';
  521. } else if (stateText.includes('degraded')) {
  522. return 'ui yellow exclamation circle icon';
  523. } else if (stateText.includes('inactive')) {
  524. return 'ui grey check sync icon';
  525. } else if (stateText.includes('active')) {
  526. return 'ui green check circle icon';
  527. } else if (stateText.includes('clean')) {
  528. return 'ui green check circle icon';
  529. } else if (stateText.includes('spare')) {
  530. return 'ui blue check circle icon';
  531. } else if (stateText.includes('reshape')) {
  532. return 'ui blue loading sync icon';
  533. } else if (stateText.includes('frozen')) {
  534. return 'ui blue snowflake icon';
  535. } else {
  536. return 'ui blue question circle icon';
  537. }
  538. }
  539. //Reload the current raid volume detail
  540. function reloadRAIDVolDetail(){
  541. if (raidManager.editingArray != undefined){
  542. initRAIDVolList(raidManager.editingArray);
  543. }
  544. }
  545. function capitalize(string) {
  546. return string.charAt(0).toUpperCase() + string.slice(1);
  547. }
  548. function loadRAIDVolDetail(deviceName){
  549. function bytesToSize(bytes) {
  550. var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
  551. if (bytes == 0) return 'n/a';
  552. var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
  553. if (i == 0) return bytes + ' ' + sizes[i];
  554. return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i];
  555. };
  556. function uuidv4() {
  557. return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
  558. (+c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16)
  559. );
  560. }
  561. //Highlight the vol selected
  562. $(".raidVol").removeClass('active');
  563. $(".raidVol").each(function(){
  564. if ($(this).attr("devpath") == deviceName){
  565. $(this).addClass('active');
  566. }
  567. })
  568. let deiviceSizeMap = {};
  569. let workingDiskCount = 0;
  570. //Load the device size map
  571. $.get("../../system/disk/raid/devinfo?devName=" + deviceName, function(sizemap){
  572. deiviceSizeMap = sizemap;
  573. raidManager.editingArray = deviceName;
  574. //Get the information of the device
  575. $.get("../../system/disk/raid/detail?devName=" + deviceName, function(data){
  576. if (data.error != undefined){
  577. $("#raidDiskList").html(`<h3 class="ui header">
  578. <i class="ui red circle times icon"></i>
  579. <div class="content" style="font-weight: 300;">
  580. Unable to load RAID volume information
  581. <div class="sub header">${capitalize(data.error)}</div>
  582. </div>
  583. </h3>`);
  584. return;
  585. }
  586. raidManager.raidDetails = data
  587. //Update the active disks info
  588. $("#RAIDOverviewDiskpath").html(data.DevicePath + ` <span class="mdevice">(${data.RaidLevel.toUpperCase()})</span>`);
  589. $("#RAIDOverviewDetails").text("State: " + capitalize(data.State));
  590. $("#RAIDUUID").text(data.UUID);
  591. $("#RAIDActiveDevices").text(data.ActiveDevices);
  592. $("#RAIDWorkingDevices").text(data.WorkingDevices);
  593. $("#RAIDFailedDevices").text(data.FailedDevices);
  594. $("#RAIDSpareDevices").text(data.SpareDevices);
  595. $("#RAIDOverviewStateIcon").attr("class", getStateIconFromStateText(data.State));
  596. //Update the RAIDVol Button on the left menu as well
  597. $(".raidVol").each(function(){
  598. if ($(this).attr("devpath") == data.DevicePath){
  599. //this is the one we are updating
  600. $(this).find(".healthIcon i").attr("class", getStateIconFromStateText(data.State))
  601. }
  602. });
  603. //Render the disk list
  604. $("#raidDiskList").html("");
  605. workingDiskCount = data.DeviceInfo.length;
  606. for (var i = 0; i < data.DeviceInfo.length; i++){
  607. let thisDeviceInfo = data.DeviceInfo[i];
  608. let raidDeviceNo = thisDeviceInfo.RaidDevice;
  609. let drivePath = thisDeviceInfo.DevicePath
  610. let driveName = drivePath.split("/").pop();
  611. let driveIcon = "drive-working.svg";
  612. let driveSize = "Unknown"; //Overall size of the disk
  613. let partitionSize = data.ArraySize * 1024; //RAID partition size, mdadm report in KiB
  614. let driveRWState = "Read Write";
  615. if (thisDeviceInfo.DevicePath = ""){
  616. driveIcon = "drive-notfound.svg"
  617. drivePath = "(Drive unplugged or damaged)"
  618. }
  619. let diskIsFailed = false;
  620. if (thisDeviceInfo.State.includes("faulty")){
  621. //Disk labeled as failed
  622. driveIcon = "drive-failed.svg"
  623. workingDiskCount--;
  624. diskIsFailed = true;
  625. }else if (thisDeviceInfo.State.includes("removed")){
  626. driveIcon = "drive-notfound.svg"
  627. workingDiskCount--;
  628. }else if (thisDeviceInfo.State.includes("rebuilding")){
  629. driveIcon = "drive-spare.svg"
  630. }
  631. if (deiviceSizeMap[driveName] == undefined){
  632. //Disk not found. Is it failed?
  633. driveSize = "";
  634. driveRWState = "";
  635. }else{
  636. driveSize = bytesToSize(deiviceSizeMap[driveName].size);
  637. driveRWState = deiviceSizeMap[driveName].ro?"<span class='readonlytag'>Read Only</span>":"Read Write";
  638. }
  639. //Handle case where raidDeviceNo is -1 (Not mounted / failed)
  640. if (raidDeviceNo == -1){
  641. if (thisDeviceInfo.State.includes("complete")){
  642. raidDeviceNo = "Rebuilding";
  643. }else{
  644. raidDeviceNo = "Faulty";
  645. }
  646. }
  647. //A random UID for this DOM element to make handling easier
  648. let raiddeviceUID = uuidv4();
  649. $("#raidDiskList").append(`<div class="ui basic segment raiddevice ${raiddeviceUID}" domid="${raiddeviceUID}">
  650. <h4 class="ui header">
  651. <img src="../disk/raid/img/${driveIcon}">
  652. <div class="content">
  653. ${raidDeviceNo}: <span class="raidMemberLabel ${driveName}"></span><br>
  654. <small><i class="ui yellow folder icon" style="margin-right: 0.3em;"></i>${drivePath!=""?drivePath:`<span class="notMountedLabel">[Not Mounted]</span>`}</small>
  655. <div class="sub header">${thisDeviceInfo.State.join(" | ")}</div>
  656. </div>
  657. </h4>
  658. <div class="capinfo">
  659. <i class="chart pie icon"></i>${bytesToSize(partitionSize)} | <i class="hdd outline icon"></i> ${driveSize}<br>
  660. <span style="font-size: 0.8em;">${driveRWState}</span>
  661. </div>
  662. <div class="raidDeviceOptions">
  663. <div class="content">
  664. <button class="ui tiny basic button removeDiskBtn ${diskIsFailed?"faulty":"normal"}" onclick="removeDisk('${deviceName}','${driveName}');"><i class="ui red trash icon"></i> Remove Disk</button>
  665. </div>
  666. </div>
  667. </div>`);
  668. if (driveName != "" && drivePath != ""){
  669. $(".raidMemberLabel." + driveName).html(`<i class="ui loading circle notch icon"></i> Resolving disk label`);
  670. resolveDiskLabelToDOM(drivePath, ".raidMemberLabel." + driveName);
  671. }else{
  672. //Invalid disk, e.g. Removed
  673. $(".raiddevice." + raiddeviceUID).find(".raidDeviceOptions").hide();
  674. }
  675. }
  676. //Check if the current setup is already at the min no. of disk
  677. let allowRemoveDisk = true;
  678. if (data.RaidLevel == "raid0"){
  679. //Do not allow for disk remove
  680. allowRemoveDisk = false;
  681. }else if (data.RaidLevel == "raid1" && workingDiskCount < 2){
  682. allowRemoveDisk = false;
  683. }else if (data.RaidLevel == "raid5" && workingDiskCount < 3){
  684. allowRemoveDisk = false;
  685. }else if (data.RaidLevel == "raid6" && workingDiskCount < 4){
  686. allowRemoveDisk = false;
  687. }
  688. if (!allowRemoveDisk){
  689. $(".removeDiskBtn.normal").addClass("disabled");
  690. $("#raidDataLossWarning").show();
  691. }else{
  692. $("#raidDataLossWarning").hide();
  693. }
  694. });
  695. });
  696. }
  697. function resolveDiskLabelToDOM(diskPath, domSelector){
  698. $.get("../../system/disk/devices/model?devName=" + diskPath, function(data){
  699. let diskLabelName = ""
  700. if (data.error == undefined){
  701. //[0] is disk labeled name
  702. //[1] is disk labeled size
  703. diskLabelName = data[0];
  704. }
  705. $(domSelector).html(diskLabelName);
  706. });
  707. }
  708. //Initialize the RAID volume list on this system
  709. //Give selectMDName for auto selecting a volume
  710. function initRAIDVolList(selectMDName=""){
  711. $("#raidVolList").html(`<p style="text-align: center;"><i class="ui loading spinner icon"></i></p>`);
  712. $.get("../../system/disk/raid/list", function(data){
  713. if (data.error != undefined){
  714. $("#raidVolList").html(`<p style="text-align: center;"><i class="ui red circle remove icon"></i> ${data.error}</p>`);
  715. return;
  716. }
  717. $("#raidVolList").html("");
  718. let containUnhealthyDisk = false;
  719. for (var i = 0; i < data.length; i++){
  720. let thisDiskInfo = data[i];
  721. $("#raidVolList").append(`<div class="ui segment raidVol" devpath="${thisDiskInfo.DevicePath}">
  722. <h4 class="ui header">
  723. <img src="../disk/raid/img/raid.svg">
  724. <div class="healthIcon"><i class="${getStateIconFromStateText(thisDiskInfo.State)}"></i></div>
  725. <div class="content" style="margin-left: 0.6em;">
  726. <span>${thisDiskInfo.DevicePath}</span> <span class="mdevice">(${thisDiskInfo.RaidLevel.toUpperCase()})</span>
  727. <div class="sub header mdevuuid" style="margin-top: 0.4em;">
  728. ${thisDiskInfo.UUID}
  729. </div>
  730. </div>
  731. </h4>
  732. </div>`);
  733. if (!isHealthy(thisDiskInfo.State)){
  734. containUnhealthyDisk = true;
  735. }
  736. console.log(thisDiskInfo);
  737. }
  738. if (data.length == 0){
  739. $("#raidVolList").html(`<div><i class="ui green check circle icon"></i> No RAID Volumes</div>`);
  740. $("#volumeDetail").hide();
  741. $("#noRAIDVolumeWarning").show();
  742. }else{
  743. $("#volumeDetail").show();
  744. $("#noRAIDVolumeWarning").hide();
  745. //Load the first RAID vol info
  746. if (selectMDName){
  747. loadRAIDVolDetail(selectMDName);
  748. }else{
  749. loadRAIDVolDetail(data[0].DevicePath);
  750. }
  751. }
  752. $("#RAIDvolCount").text(data.length);
  753. //Update the overall health text
  754. if (containUnhealthyDisk){
  755. $("#RAIDhealthyState").html("<i class='ui yellow exclamation triangle icon'></i> Attention Needed!");
  756. }else{
  757. $("#RAIDhealthyState").html("<i class='ui green circle check icon'></i> Healthy");
  758. }
  759. });
  760. }
  761. initRAIDVolList();
  762. /* Force unload all RAID volume and remount them from config files */
  763. function assembleRaidVolumes(){
  764. var apiObject = {
  765. api: "../system/disk/raid/assemble",
  766. data: {},
  767. title: `<i class='yellow exclamation triangle icon'></i> Force Assemble`,
  768. desc: `Confirm force stop & reload RAID from mdadm.conf?`,
  769. thisuser: true, //This username as default, set to false for entering other user
  770. method: "POST",
  771. success: undefined
  772. }
  773. apiObject = encodeURIComponent(JSON.stringify(apiObject));
  774. parent.newFloatWindow({
  775. url: "SystemAO/security/authreq.html#" + apiObject,
  776. width: 480,
  777. height: 300,
  778. appicon: "SystemAO/security/img/lock.svg",
  779. title: `Confirm Force Assemble`,
  780. parent: ao_module_windowID,
  781. callback: "handleForceAssembleCallback"
  782. });
  783. }
  784. window.handleForceAssembleCallback = function(data){
  785. if (data.error != undefined){
  786. //Something went wrong
  787. msgbox(capitalize(data.error), false, 5000);
  788. }else{
  789. setTimeout(function(){
  790. //Reload all RAID volumes
  791. raidManager = {};
  792. initRAIDVolList();
  793. if (data != false){
  794. msgbox("Force RAID Assemble Completed");
  795. }
  796. }, 300);
  797. }
  798. }
  799. </script>
  800. </body>
  801. </html>