From 3f272b36d4c6d88b6a8efecc5eda8c3f3afea101 Mon Sep 17 00:00:00 2001 From: Jason Kulatunga Date: Sat, 30 Jul 2022 08:50:23 -0700 Subject: [PATCH] adding setting to allow users to customize between binary vs SI/Metric units in UI. fixes #330 --- .../migrations/m20220716214900/setting.go | 1 + .../scrutiny_repository_migrations.go | 6 ++ .../database/scrutiny_repository_settings.go | 6 +- webapp/backend/pkg/models/setting_entry.go | 1 + webapp/backend/pkg/models/settings.go | 1 + .../src/app/core/config/app.config.ts | 4 + .../dashboard-device.component.html | 3 +- .../dashboard-settings.component.html | 10 ++- .../dashboard-settings.component.ts | 3 + .../app/modules/detail/detail.component.html | 2 +- .../src/app/shared/file-size.pipe.spec.ts | 60 ++++++++++--- .../frontend/src/app/shared/file-size.pipe.ts | 84 ++++--------------- 12 files changed, 100 insertions(+), 81 deletions(-) diff --git a/webapp/backend/pkg/database/migrations/m20220716214900/setting.go b/webapp/backend/pkg/database/migrations/m20220716214900/setting.go index 9c1f746..70d8d5e 100644 --- a/webapp/backend/pkg/database/migrations/m20220716214900/setting.go +++ b/webapp/backend/pkg/database/migrations/m20220716214900/setting.go @@ -14,4 +14,5 @@ type Setting struct { SettingValueNumeric int `json:"setting_value_numeric"` SettingValueString string `json:"setting_value_string"` + SettingValueBool bool `json:"setting_value_bool"` } diff --git a/webapp/backend/pkg/database/scrutiny_repository_migrations.go b/webapp/backend/pkg/database/scrutiny_repository_migrations.go index a6f1b68..99eb1f3 100644 --- a/webapp/backend/pkg/database/scrutiny_repository_migrations.go +++ b/webapp/backend/pkg/database/scrutiny_repository_migrations.go @@ -319,6 +319,12 @@ func (sr *scrutinyRepository) Migrate(ctx context.Context) error { SettingDataType: "string", SettingValueString: "celsius", }, + { + SettingKeyName: "file_size_si_units", + SettingKeyDescription: "File size in SI units (true | false)", + SettingDataType: "bool", + SettingValueBool: false, + }, { SettingKeyName: "metrics.notify_level", diff --git a/webapp/backend/pkg/database/scrutiny_repository_settings.go b/webapp/backend/pkg/database/scrutiny_repository_settings.go index 918a9f4..d92ce9b 100644 --- a/webapp/backend/pkg/database/scrutiny_repository_settings.go +++ b/webapp/backend/pkg/database/scrutiny_repository_settings.go @@ -24,6 +24,8 @@ func (sr *scrutinyRepository) LoadSettings(ctx context.Context) (*models.Setting sr.appConfig.SetDefault(configKey, settingsEntry.SettingValueNumeric) } else if settingsEntry.SettingDataType == "string" { sr.appConfig.SetDefault(configKey, settingsEntry.SettingValueString) + } else if settingsEntry.SettingDataType == "bool" { + sr.appConfig.SetDefault(configKey, settingsEntry.SettingValueBool) } } @@ -67,11 +69,13 @@ func (sr *scrutinyRepository) SaveSettings(ctx context.Context, settings models. settingsEntries[ndx].SettingValueNumeric = sr.appConfig.GetInt(configKey) } else if settingsEntry.SettingDataType == "string" { settingsEntries[ndx].SettingValueString = sr.appConfig.GetString(configKey) + } else if settingsEntry.SettingDataType == "bool" { + settingsEntries[ndx].SettingValueBool = sr.appConfig.GetBool(configKey) } // store in database. //TODO: this should be `sr.gormClient.Updates(&settingsEntries).Error` - err := sr.gormClient.Model(&models.SettingEntry{}).Where([]uint{settingsEntry.ID}).Select("setting_value_numeric", "setting_value_string").Updates(settingsEntries[ndx]).Error + err := sr.gormClient.Model(&models.SettingEntry{}).Where([]uint{settingsEntry.ID}).Select("setting_value_numeric", "setting_value_string", "setting_value_bool").Updates(settingsEntries[ndx]).Error if err != nil { return err } diff --git a/webapp/backend/pkg/models/setting_entry.go b/webapp/backend/pkg/models/setting_entry.go index 48d2c4c..2ac78f6 100644 --- a/webapp/backend/pkg/models/setting_entry.go +++ b/webapp/backend/pkg/models/setting_entry.go @@ -15,6 +15,7 @@ type SettingEntry struct { SettingValueNumeric int `json:"setting_value_numeric"` SettingValueString string `json:"setting_value_string"` + SettingValueBool bool `json:"setting_value_bool"` } func (s SettingEntry) TableName() string { diff --git a/webapp/backend/pkg/models/settings.go b/webapp/backend/pkg/models/settings.go index 48ba2d5..f06db84 100644 --- a/webapp/backend/pkg/models/settings.go +++ b/webapp/backend/pkg/models/settings.go @@ -13,6 +13,7 @@ type Settings struct { DashboardDisplay string `json:"dashboard_display" mapstructure:"dashboard_display"` DashboardSort string `json:"dashboard_sort" mapstructure:"dashboard_sort"` TemperatureUnit string `json:"temperature_unit" mapstructure:"temperature_unit"` + FileSizeSIUnits bool `json:"file_size_si_units" mapstructure:"file_size_si_units"` Metrics struct { NotifyLevel int `json:"notify_level" mapstructure:"notify_level"` diff --git a/webapp/frontend/src/app/core/config/app.config.ts b/webapp/frontend/src/app/core/config/app.config.ts index b4a6114..92f0451 100644 --- a/webapp/frontend/src/app/core/config/app.config.ts +++ b/webapp/frontend/src/app/core/config/app.config.ts @@ -43,6 +43,8 @@ export interface AppConfig { temperature_unit?: TemperatureUnit; + file_size_si_units?: boolean; + // Settings from Scrutiny API metrics?: { @@ -69,6 +71,8 @@ export const appConfig: AppConfig = { dashboard_sort: 'status', temperature_unit: 'celsius', + file_size_si_units: false, + metrics: { notify_level: MetricsNotifyLevel.Fail, status_filter_attributes: MetricsStatusFilterAttributes.All, diff --git a/webapp/frontend/src/app/layout/common/dashboard-device/dashboard-device.component.html b/webapp/frontend/src/app/layout/common/dashboard-device/dashboard-device.component.html index 7dfe40c..43e8964 100644 --- a/webapp/frontend/src/app/layout/common/dashboard-device/dashboard-device.component.html +++ b/webapp/frontend/src/app/layout/common/dashboard-device/dashboard-device.component.html @@ -58,7 +58,8 @@
Capacity
-
{{ deviceSummary.device.capacity | fileSize}}
+
{{ deviceSummary.device.capacity | fileSize:config.file_size_si_units}}
Powered On
diff --git a/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.html b/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.html index cde830a..750d54d 100644 --- a/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.html +++ b/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.html @@ -37,12 +37,20 @@
- Temperature Display Unit + Temperature Celsius Fahrenheit + + + File Size + + SI Units (GB) + Binary Units (GiB) + +
diff --git a/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.ts b/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.ts index 21e8e8e..6bc5f2a 100644 --- a/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.ts +++ b/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.ts @@ -22,6 +22,7 @@ export class DashboardSettingsComponent implements OnInit { dashboardDisplay: string; dashboardSort: string; temperatureUnit: string; + fileSizeSIUnits: boolean; theme: string; statusThreshold: number; statusFilterAttributes: number; @@ -46,6 +47,7 @@ export class DashboardSettingsComponent implements OnInit { this.dashboardDisplay = config.dashboard_display; this.dashboardSort = config.dashboard_sort; this.temperatureUnit = config.temperature_unit; + this.fileSizeSIUnits = config.file_size_si_units; this.theme = config.theme; this.statusFilterAttributes = config.metrics.status_filter_attributes; @@ -60,6 +62,7 @@ export class DashboardSettingsComponent implements OnInit { dashboard_display: this.dashboardDisplay as DashboardDisplay, dashboard_sort: this.dashboardSort as DashboardSort, temperature_unit: this.temperatureUnit as TemperatureUnit, + file_size_si_units: this.fileSizeSIUnits, theme: this.theme as Theme, metrics: { status_filter_attributes: this.statusFilterAttributes as MetricsStatusFilterAttributes, diff --git a/webapp/frontend/src/app/modules/detail/detail.component.html b/webapp/frontend/src/app/modules/detail/detail.component.html index d2ff848..5c94592 100644 --- a/webapp/frontend/src/app/modules/detail/detail.component.html +++ b/webapp/frontend/src/app/modules/detail/detail.component.html @@ -107,7 +107,7 @@
Firmware Version
-
{{device?.capacity | fileSize}}
+
{{device?.capacity | fileSize:config.file_size_si_units}}
Capacity
diff --git a/webapp/frontend/src/app/shared/file-size.pipe.spec.ts b/webapp/frontend/src/app/shared/file-size.pipe.spec.ts index 14973cf..0f2127a 100644 --- a/webapp/frontend/src/app/shared/file-size.pipe.spec.ts +++ b/webapp/frontend/src/app/shared/file-size.pipe.spec.ts @@ -1,4 +1,4 @@ -import { FileSizePipe } from './file-size.pipe'; +import {FileSizePipe} from './file-size.pipe'; describe('FileSizePipe', () => { it('create an instance', () => { @@ -10,23 +10,61 @@ describe('FileSizePipe', () => { const testCases = [ { 'bytes': 1500, - 'precision': undefined, - 'result': '1 KB' - },{ - 'bytes': 2_100_000_000, - 'precision': undefined, - 'result': '2.0 GB', - },{ + 'si': false, + 'result': '1.5 KiB' + }, + { 'bytes': 1500, - 'precision': 2, - 'result': '1.46 KB', + 'si': true, + 'result': '1.5 kB' + }, + { + 'bytes': 5000, + 'si': false, + 'result': '4.9 KiB', + }, + { + 'bytes': 5000, + 'si': true, + 'result': '5.0 kB', + }, + { + 'bytes': 999_949, + 'si': false, + 'result': '976.5 KiB', + }, + { + 'bytes': 999_949, + 'si': true, + 'result': '999.9 kB', + }, + { + 'bytes': 999_950, + 'si': true, + 'result': '1.0 MB', + }, + { + 'bytes': 1_551_859_712, + 'si': false, + 'result': '1.4 GiB', + }, + { + 'bytes': 2_100_000_000, + 'si': false, + 'result': '2.0 GiB', + }, + { + 'bytes': 2_100_000_000, + 'si': true, + 'result': '2.1 GB', } ] + testCases.forEach((test, index) => { it(`should correctly format bytes ${test.bytes}. (testcase: ${index + 1})`, () => { // test const pipe = new FileSizePipe(); - const formatted = pipe.transform(test.bytes, test.precision) + const formatted = pipe.transform(test.bytes, test.si) expect(formatted).toEqual(test.result); }); }) diff --git a/webapp/frontend/src/app/shared/file-size.pipe.ts b/webapp/frontend/src/app/shared/file-size.pipe.ts index e6cbc7b..14fdf0c 100644 --- a/webapp/frontend/src/app/shared/file-size.pipe.ts +++ b/webapp/frontend/src/app/shared/file-size.pipe.ts @@ -1,75 +1,27 @@ -/** - * @license - * Copyright (c) 2019 Jonathan Catmull. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -import { Pipe, PipeTransform } from '@angular/core'; +import {Pipe, PipeTransform} from '@angular/core'; -type unit = 'bytes' | 'KB' | 'MB' | 'GB' | 'TB' | 'PB'; -type unitPrecisionMap = { - [u in unit]: number; -}; - -const defaultPrecisionMap: unitPrecisionMap = { - bytes: 0, - KB: 0, - MB: 1, - GB: 1, - TB: 2, - PB: 2 -}; - -/* - * Convert bytes into largest possible unit. - * Takes an precision argument that can be a number or a map for each unit. - * Usage: - * bytes | fileSize:precision - * @example - * // returns 1 KB - * {{ 1500 | fileSize }} - * @example - * // returns 2.1 GB - * {{ 2100000000 | fileSize }} - * @example - * // returns 1.46 KB - * {{ 1500 | fileSize:2 }} - */ -@Pipe({ name: 'fileSize' }) +@Pipe({name: 'fileSize'}) export class FileSizePipe implements PipeTransform { - private readonly units: unit[] = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB']; - - transform(bytes: number = 0, precision: number | unitPrecisionMap = defaultPrecisionMap): string { - if (isNaN(parseFloat(String(bytes))) || !isFinite(bytes)) return '?'; - let unitIndex = 0; + transform(bytes: number = 0, si = false, dp = 1): string { + const thresh = si ? 1000 : 1024; - while (bytes >= 1024) { - bytes /= 1024; - unitIndex++; + if (Math.abs(bytes) < thresh) { + return bytes + ' B'; } - const unit = this.units[unitIndex]; + const units = si + ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] + : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; + let u = -1; + const r = 10 ** dp; - if (typeof precision === 'number') { - return `${bytes.toFixed(+precision)} ${unit}`; - } - return `${bytes.toFixed(precision[unit])} ${unit}`; + do { + bytes /= thresh; + ++u; + } while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1); + + + return bytes.toFixed(dp) + ' ' + units[u]; } }