diff --git a/sysutils/smart/src/opnsense/www/js/widgets/Smart.js b/sysutils/smart/src/opnsense/www/js/widgets/Smart.js
index 30b2d5986..03bf46f57 100644
--- a/sysutils/smart/src/opnsense/www/js/widgets/Smart.js
+++ b/sysutils/smart/src/opnsense/www/js/widgets/Smart.js
@@ -28,34 +28,59 @@ export default class Smart extends BaseTableWidget {
constructor() {
super();
this.tickTimeout = 300;
- this.disks = null;
}
getMarkup() {
- const $container = $('
');
- const $smarttable = this.createTable('smart-table', {
- headerPosition: 'left',
+ const $table = this.createTable('smart-table', {
+ headers: ['Device', 'SMART Status']
});
- $container.append($smarttable);
- return $container;
+ $table.addClass('table table-striped table-condensed table-hover');
+ return $('').append($table);
+ }
+
+ getAttrRaw(output, id) {
+ const attr = output?.ata_smart_attributes?.table?.find(a => a.id === id);
+ return attr?.raw?.string ?? 'N/A';
}
async onWidgetTick() {
- let disks = await this.ajaxCall(`/api/smart/service/${'list/detailed'}`, {}, 'POST');
- if (disks && disks.devices) {
- const rows = [];
- for (const device of disks.devices) {
- try {
- const health = device.state.smart_status.passed;
- const text = health ? "OK" : "FAILED";
- const css = { color: health ? "green" : "red", fontSize: '150%' };
- const field = $(``).text(text).css(css).prop('outerHTML');
- rows.push([[device.device], field]);
- } catch (error) {
- super.updateTable('smart-table', [[["Error"], $(`${this.translations.nosmart} ${device.device}: ${error}`).prop('outerHTML')]]);
+ try {
+ const {devices = []} = await this.ajaxCall('/api/smart/service/list/detailed', {}, 'POST') || {};
+
+ const rows = await Promise.all(devices.map(async dev => {
+ const name = dev.device || 'Unknown';
+ const ident = dev.ident || 'N/A';
+ let status = 'Unknown';
+ let tip = `Device: ${name}\nSerial: ${ident}\n\nKey attributes:`;
+
+ const health = dev.state?.smart_status?.passed;
+ if (health !== undefined) {
+ const icon = health ? 'check-circle text-success' : 'exclamation-circle text-danger';
+ status = ` ${health ? 'OK' : 'FAILED'}`;
}
- }
- super.updateTable('smart-table', rows);
+
+ const resp = await this.ajaxCall('/api/smart/service/info', JSON.stringify({device: name, type: 'a', json: '1'}), 'POST');
+ const output = resp?.output;
+
+ if (output) {
+ tip += `\nTemp: ${output.temperature?.current ?? 'N/A'} °C`;
+ tip += `\nPower-On: ${output.power_on_time?.hours ?? 'N/A'} h`;
+ tip += `\nReallocated: ${this.getAttrRaw(output, 5)}`;
+ tip += `\nPending: ${this.getAttrRaw(output, 197)}`;
+ tip += `\nUncorrectable: ${this.getAttrRaw(output, 198)}`;
+ tip += `\nCRC Errors: ${this.getAttrRaw(output, 199)}`;
+ } else {
+ tip += '\n(No details available)';
+ }
+
+ const escaped = tip.replace(/"/g, '"').replace(/\n/g, '
');
+ return [`${name}`, status];
+ }));
+
+ super.updateTable('smart-table', rows.sort((a,b) => a[0].localeCompare(b[0])));
+ $('[data-toggle="tooltip"]').tooltip({container: 'body'});
+ } catch {
+ super.updateTable('smart-table', [['Error', 'Widget failed']]);
}
}
}