Skip to content

Commit

Permalink
on settings save, return the new settings.
Browse files Browse the repository at this point in the history
update the frontend to persist settings to the database.
Using ScrutinyConfigService instead of TreoConfigService.
Using snake case settings in frontend.
Make sure we're using AppConfig type where possible.
  • Loading branch information
AnalogJ committed Jul 23, 2022
1 parent 7e672e8 commit a12ba7c
Show file tree
Hide file tree
Showing 16 changed files with 259 additions and 89 deletions.
3 changes: 2 additions & 1 deletion webapp/backend/pkg/web/handler/save_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func SaveSettings(c *gin.Context) {
}

c.JSON(http.StatusOK, gin.H{
"success": true,
"success": true,
"settings": settings,
})
}
18 changes: 9 additions & 9 deletions webapp/backend/pkg/web/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func (suite *ServerTestSuite) TestHealthRoute() {
mockCtrl := gomock.NewController(suite.T())
defer mockCtrl.Finish()
fakeConfig := mock_config.NewMockInterface(mockCtrl)
fakeConfig.EXPECT().Set(gomock.Any(), gomock.Any()).AnyTimes()
fakeConfig.EXPECT().SetDefault(gomock.Any(), gomock.Any()).AnyTimes()
fakeConfig.EXPECT().UnmarshalKey(gomock.Any(), gomock.Any()).AnyTimes().Return(nil)
fakeConfig.EXPECT().GetString("web.database.location").Return(path.Join(parentPath, "scrutiny_test.db")).AnyTimes()
fakeConfig.EXPECT().GetString("web.src.frontend.path").Return(parentPath).AnyTimes()
Expand Down Expand Up @@ -134,7 +134,7 @@ func (suite *ServerTestSuite) TestRegisterDevicesRoute() {
mockCtrl := gomock.NewController(suite.T())
defer mockCtrl.Finish()
fakeConfig := mock_config.NewMockInterface(mockCtrl)
fakeConfig.EXPECT().Set(gomock.Any(), gomock.Any()).AnyTimes()
fakeConfig.EXPECT().SetDefault(gomock.Any(), gomock.Any()).AnyTimes()
fakeConfig.EXPECT().UnmarshalKey(gomock.Any(), gomock.Any()).AnyTimes().Return(nil)
fakeConfig.EXPECT().GetString("web.database.location").Return(path.Join(parentPath, "scrutiny_test.db")).AnyTimes()
fakeConfig.EXPECT().GetString("web.src.frontend.path").Return(parentPath).AnyTimes()
Expand Down Expand Up @@ -176,7 +176,7 @@ func (suite *ServerTestSuite) TestUploadDeviceMetricsRoute() {
mockCtrl := gomock.NewController(suite.T())
defer mockCtrl.Finish()
fakeConfig := mock_config.NewMockInterface(mockCtrl)
fakeConfig.EXPECT().Set(gomock.Any(), gomock.Any()).AnyTimes()
fakeConfig.EXPECT().SetDefault(gomock.Any(), gomock.Any()).AnyTimes()
fakeConfig.EXPECT().UnmarshalKey(gomock.Any(), gomock.Any()).AnyTimes().Return(nil)
fakeConfig.EXPECT().GetString("web.database.location").AnyTimes().Return(path.Join(parentPath, "scrutiny_test.db"))
fakeConfig.EXPECT().GetString("web.src.frontend.path").AnyTimes().Return(parentPath)
Expand Down Expand Up @@ -228,7 +228,7 @@ func (suite *ServerTestSuite) TestPopulateMultiple() {
mockCtrl := gomock.NewController(suite.T())
defer mockCtrl.Finish()
fakeConfig := mock_config.NewMockInterface(mockCtrl)
fakeConfig.EXPECT().Set(gomock.Any(), gomock.Any()).AnyTimes()
fakeConfig.EXPECT().SetDefault(gomock.Any(), gomock.Any()).AnyTimes()
fakeConfig.EXPECT().UnmarshalKey(gomock.Any(), gomock.Any()).AnyTimes().Return(nil)
//fakeConfig.EXPECT().GetString("web.database.location").AnyTimes().Return("testdata/scrutiny_test.db")
fakeConfig.EXPECT().GetStringSlice("notify.urls").Return([]string{}).AnyTimes()
Expand Down Expand Up @@ -331,7 +331,7 @@ func (suite *ServerTestSuite) TestSendTestNotificationRoute_WebhookFailure() {
mockCtrl := gomock.NewController(suite.T())
defer mockCtrl.Finish()
fakeConfig := mock_config.NewMockInterface(mockCtrl)
fakeConfig.EXPECT().Set(gomock.Any(), gomock.Any()).AnyTimes()
fakeConfig.EXPECT().SetDefault(gomock.Any(), gomock.Any()).AnyTimes()
fakeConfig.EXPECT().UnmarshalKey(gomock.Any(), gomock.Any()).AnyTimes().Return(nil)
fakeConfig.EXPECT().GetString("web.database.location").AnyTimes().Return(path.Join(parentPath, "scrutiny_test.db"))
fakeConfig.EXPECT().GetString("web.src.frontend.path").AnyTimes().Return(parentPath)
Expand Down Expand Up @@ -376,7 +376,7 @@ func (suite *ServerTestSuite) TestSendTestNotificationRoute_ScriptFailure() {
mockCtrl := gomock.NewController(suite.T())
defer mockCtrl.Finish()
fakeConfig := mock_config.NewMockInterface(mockCtrl)
fakeConfig.EXPECT().Set(gomock.Any(), gomock.Any()).AnyTimes()
fakeConfig.EXPECT().SetDefault(gomock.Any(), gomock.Any()).AnyTimes()
fakeConfig.EXPECT().UnmarshalKey(gomock.Any(), gomock.Any()).AnyTimes().Return(nil)
fakeConfig.EXPECT().GetString("web.database.location").AnyTimes().Return(path.Join(parentPath, "scrutiny_test.db"))
fakeConfig.EXPECT().GetString("web.src.frontend.path").AnyTimes().Return(parentPath)
Expand Down Expand Up @@ -421,7 +421,7 @@ func (suite *ServerTestSuite) TestSendTestNotificationRoute_ScriptSuccess() {
mockCtrl := gomock.NewController(suite.T())
defer mockCtrl.Finish()
fakeConfig := mock_config.NewMockInterface(mockCtrl)
fakeConfig.EXPECT().Set(gomock.Any(), gomock.Any()).AnyTimes()
fakeConfig.EXPECT().SetDefault(gomock.Any(), gomock.Any()).AnyTimes()
fakeConfig.EXPECT().UnmarshalKey(gomock.Any(), gomock.Any()).AnyTimes().Return(nil)
fakeConfig.EXPECT().GetString("web.database.location").AnyTimes().Return(path.Join(parentPath, "scrutiny_test.db"))
fakeConfig.EXPECT().GetString("web.src.frontend.path").AnyTimes().Return(parentPath)
Expand Down Expand Up @@ -466,7 +466,7 @@ func (suite *ServerTestSuite) TestSendTestNotificationRoute_ShoutrrrFailure() {
mockCtrl := gomock.NewController(suite.T())
defer mockCtrl.Finish()
fakeConfig := mock_config.NewMockInterface(mockCtrl)
fakeConfig.EXPECT().Set(gomock.Any(), gomock.Any()).AnyTimes()
fakeConfig.EXPECT().SetDefault(gomock.Any(), gomock.Any()).AnyTimes()
fakeConfig.EXPECT().UnmarshalKey(gomock.Any(), gomock.Any()).AnyTimes().Return(nil)
fakeConfig.EXPECT().GetString("web.database.location").AnyTimes().Return(path.Join(parentPath, "scrutiny_test.db"))
fakeConfig.EXPECT().GetString("web.src.frontend.path").AnyTimes().Return(parentPath)
Expand Down Expand Up @@ -510,7 +510,7 @@ func (suite *ServerTestSuite) TestGetDevicesSummaryRoute_Nvme() {
mockCtrl := gomock.NewController(suite.T())
defer mockCtrl.Finish()
fakeConfig := mock_config.NewMockInterface(mockCtrl)
fakeConfig.EXPECT().Set(gomock.Any(), gomock.Any()).AnyTimes()
fakeConfig.EXPECT().SetDefault(gomock.Any(), gomock.Any()).AnyTimes()
fakeConfig.EXPECT().UnmarshalKey(gomock.Any(), gomock.Any()).AnyTimes().Return(nil)
fakeConfig.EXPECT().GetString("web.database.location").AnyTimes().Return(path.Join(parentPath, "scrutiny_test.db"))
fakeConfig.EXPECT().GetString("web.src.frontend.path").AnyTimes().Return(parentPath)
Expand Down
34 changes: 17 additions & 17 deletions webapp/frontend/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { NgModule, enableProdMode } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ExtraOptions, PreloadAllModules, RouterModule } from '@angular/router';
import { APP_BASE_HREF } from '@angular/common';
import { MarkdownModule } from 'ngx-markdown';
import { TreoModule } from '@treo';
import { TreoConfigModule } from '@treo/services/config';
import { TreoMockApiModule } from '@treo/lib/mock-api';
import { CoreModule } from 'app/core/core.module';
import { appConfig } from 'app/core/config/app.config';
import { mockDataServices } from 'app/data/mock';
import { LayoutModule } from 'app/layout/layout.module';
import { AppComponent } from 'app/app.component';
import { appRoutes, getAppBaseHref } from 'app/app.routing';
import {enableProdMode, NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {ExtraOptions, PreloadAllModules, RouterModule} from '@angular/router';
import {APP_BASE_HREF} from '@angular/common';
import {MarkdownModule} from 'ngx-markdown';
import {TreoModule} from '@treo';
import {ScrutinyConfigModule} from 'app/core/config/scrutiny-config.module';
import {TreoMockApiModule} from '@treo/lib/mock-api';
import {CoreModule} from 'app/core/core.module';
import {appConfig} from 'app/core/config/app.config';
import {mockDataServices} from 'app/data/mock';
import {LayoutModule} from 'app/layout/layout.module';
import {AppComponent} from 'app/app.component';
import {appRoutes, getAppBaseHref} from 'app/app.routing';

const routerConfig: ExtraOptions = {
scrollPositionRestoration: 'enabled',
preloadingStrategy : PreloadAllModules
preloadingStrategy: PreloadAllModules
};

let dev = [
Expand All @@ -41,7 +41,7 @@ if (process.env.NODE_ENV === 'production') {

// Treo & Treo Mock API
TreoModule,
TreoConfigModule.forRoot(appConfig),
ScrutinyConfigModule.forRoot(appConfig),
...dev,

// Core
Expand Down
51 changes: 42 additions & 9 deletions webapp/frontend/src/app/core/config/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,47 @@ export type DashboardSort = 'status' | 'title' | 'age'

export type TemperatureUnit = 'celsius' | 'fahrenheit'


enum MetricsNotifyLevel {
Warn = 1,
Fail = 2
}

enum MetricsStatusFilterAttributes {
All = 0,
Critical = 1
}

enum MetricsStatusThreshold {
Smart = 1,
Scrutiny = 2,

// shortcut
Both = 3
}

/**
* AppConfig interface. Update this interface to strictly type your config
* object.
*/
export interface AppConfig {
theme: Theme;
layout: Layout;
theme?: Theme;
layout?: Layout;

// Dashboard options
dashboardDisplay: DashboardDisplay;
dashboardSort: DashboardSort;
dashboard_display?: DashboardDisplay;
dashboard_sort?: DashboardSort;

temperature_unit?: TemperatureUnit;

// Settings from Scrutiny API

metrics?: {
notify_level?: MetricsNotifyLevel
status_filter_attributes?: MetricsStatusFilterAttributes
status_threshold?: MetricsStatusThreshold
}

temperatureUnit: TemperatureUnit;
}

/**
Expand All @@ -34,12 +62,17 @@ export interface AppConfig {
* "ConfigService".
*/
export const appConfig: AppConfig = {
theme : 'light',
theme: 'light',
layout: 'material',

dashboardDisplay: 'name',
dashboardSort: 'status',
dashboard_display: 'name',
dashboard_sort: 'status',

temperatureUnit: 'celsius',
temperature_unit: 'celsius',
metrics: {
notify_level: MetricsNotifyLevel.Fail,
status_filter_attributes: MetricsStatusFilterAttributes.All,
status_threshold: MetricsStatusThreshold.Both
}
};

33 changes: 33 additions & 0 deletions webapp/frontend/src/app/core/config/scrutiny-config.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {ModuleWithProviders, NgModule} from '@angular/core';
import {ScrutinyConfigService} from 'app/core/config/scrutiny-config.service';
import {TREO_APP_CONFIG} from '@treo/services/config/config.constants';

@NgModule()
export class ScrutinyConfigModule {
/**
* Constructor
*
* @param {ScrutinyConfigService} _scrutinyConfigService
*/
constructor(
private _scrutinyConfigService: ScrutinyConfigService
) {
}

/**
* forRoot method for setting user configuration
*
* @param config
*/
static forRoot(config: any): ModuleWithProviders {
return {
ngModule: ScrutinyConfigModule,
providers: [
{
provide: TREO_APP_CONFIG,
useValue: config
}
]
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {TestBed} from '@angular/core/testing';

import {ScrutinyConfigService} from './scrutiny-config.service';

describe('ScrutinyConfigService', () => {
let service: ScrutinyConfigService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(ScrutinyConfigService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
84 changes: 84 additions & 0 deletions webapp/frontend/src/app/core/config/scrutiny-config.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import {Inject, Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {TREO_APP_CONFIG} from '@treo/services/config/config.constants';
import {BehaviorSubject, Observable} from 'rxjs';
import {getBasePath} from '../../app.routing';
import {map, tap} from 'rxjs/operators';
import {AppConfig} from './app.config';
import {merge} from 'lodash';

@Injectable({
providedIn: 'root'
})
export class ScrutinyConfigService {
// Private
private _config: BehaviorSubject<AppConfig>;
private _defaultConfig: AppConfig;

constructor(
private _httpClient: HttpClient,
@Inject(TREO_APP_CONFIG) defaultConfig: AppConfig
) {
// Set the private defaults
this._defaultConfig = defaultConfig
this._config = new BehaviorSubject(null);
}


// -----------------------------------------------------------------------------------------------------
// @ Accessors
// -----------------------------------------------------------------------------------------------------

/**
* Setter & getter for config
*/
set config(value: AppConfig) {
// get the current config, merge the new values, and then submit. (setTheme only sets a single key, not the whole obj)
const mergedSettings = merge({}, this._config.getValue(), value);

console.log('saving settings...', mergedSettings)
this._httpClient.post(getBasePath() + '/api/settings', mergedSettings).pipe(
map((response: any) => {
console.log('settings resp')
return response.settings
}),
tap((settings: AppConfig) => {
this._config.next(settings);
return settings
})
).subscribe(resp => {
console.log('updated settings', resp)
})
}

get config$(): Observable<AppConfig> {
if (this._config.getValue()) {
console.log('using cached settings:', this._config.getValue())
return this._config.asObservable()
} else {
console.log('retrieving settings')
return this._httpClient.get(getBasePath() + '/api/settings').pipe(
map((response: any) => {
return response.settings
}),
tap((settings: AppConfig) => {
this._config.next(settings);
return this._config.asObservable()
})
);
}

}

// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------

/**
* Resets the config to the default
*/
reset(): void {
// Set the config
this.config = this._defaultConfig
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<div class="flex items-center">
<div class="flex flex-col">
<a [routerLink]="'/device/'+ deviceSummary.device.wwn"
class="font-bold text-md text-secondary uppercase tracking-wider">{{deviceSummary.device | deviceTitle:config.dashboardDisplay}}</a>
class="font-bold text-md text-secondary uppercase tracking-wider">{{deviceSummary.device | deviceTitle:config.dashboard_display}}</a>
<div [ngClass]="classDeviceLastUpdatedOn(deviceSummary)" class="font-medium text-sm" *ngIf="deviceSummary.smart">
Last Updated on {{deviceSummary.smart.collector_date | date:'MMMM dd, yyyy - HH:mm' }}
</div>
Expand Down Expand Up @@ -51,7 +51,8 @@
</div>
<div class="flex flex-col mx-6 my-3 xs:w-full">
<div class="font-semibold text-xs text-hint uppercase tracking-wider leading-none">Temperature</div>
<div class="mt-2 font-medium text-3xl leading-none" *ngIf="deviceSummary.smart?.collector_date; else unknownTemp">{{ deviceSummary.smart?.temp | temperature:config.temperatureUnit:true }}</div>
<div class="mt-2 font-medium text-3xl leading-none"
*ngIf="deviceSummary.smart?.collector_date; else unknownTemp">{{ deviceSummary.smart?.temp | temperature:config.temperature_unit:true }}</div>
<ng-template #unknownTemp><div class="mt-2 font-medium text-3xl leading-none">--</div></ng-template>
</div>
<div class="flex flex-col mx-6 my-3 xs:w-full">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('DashboardDeviceComponent', () => {
let fixture: ComponentFixture<DashboardDeviceComponent>;

const matDialogSpy = jasmine.createSpyObj('MatDialog', ['open']);
// const configServiceSpy = jasmine.createSpyObj('TreoConfigService', ['config$']);
// const configServiceSpy = jasmine.createSpyObj('ScrutinyConfigService', ['config$']);


beforeEach(async(() => {
Expand All @@ -28,7 +28,7 @@ describe('DashboardDeviceComponent', () => {
],
providers: [
{provide: MatDialog, useValue: matDialogSpy},
{provide: TREO_APP_CONFIG, useValue: {dashboardDisplay: 'name'}}
{provide: TREO_APP_CONFIG, useValue: {dashboard_display: 'name'}}
],
declarations: [DashboardDeviceComponent]
})
Expand Down
Loading

0 comments on commit a12ba7c

Please sign in to comment.