diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 3e796033..6b437a9d 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -23,6 +23,7 @@ import { DashboardComponent } from './components/dashboard/dashboard.component'; import { QueuesViewComponent } from './components/queues-view/queues-view.component'; import { AppsViewComponent } from './components/apps-view/apps-view.component'; import { NodesViewComponent } from './components/nodes-view/nodes-view.component'; +import { ErrorViewComponent } from './components/error-view/error-view.component'; const appRoutes: Routes = [ { @@ -45,6 +46,11 @@ const appRoutes: Routes = [ component: NodesViewComponent, data: { breadcrumb: 'Nodes' } }, + { + path: 'error', + component: ErrorViewComponent, + data: { breadcrumb: 'Error' } + }, { path: '', pathMatch: 'full', diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a384e75e..7dab8351 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -19,7 +19,7 @@ import { NgModule, APP_INITIALIZER } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { HttpClientModule } from '@angular/common/http'; +import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { NgxSpinnerModule } from 'ngx-spinner'; import { FormsModule } from '@angular/forms'; import { @@ -40,6 +40,7 @@ import { import { AppRoutingModule } from './app-routing.module'; import { envConfigFactory, EnvconfigService } from './services/envconfig/envconfig.service'; +import { ApiErrorInterceptor } from './interceptors/api-error/api-error.interceptor'; import { AppComponent } from './app.component'; import { DashboardComponent } from './components/dashboard/dashboard.component'; import { QueuesViewComponent } from './components/queues-view/queues-view.component'; @@ -52,6 +53,7 @@ import { ContainerHistoryComponent } from './components/container-history/contai import { QueueRackComponent } from './components/queue-rack/queue-rack.component'; import { AppsViewComponent } from './components/apps-view/apps-view.component'; import { NodesViewComponent } from './components/nodes-view/nodes-view.component'; +import { ErrorViewComponent } from './components/error-view/error-view.component'; @NgModule({ declarations: [ @@ -66,7 +68,8 @@ import { NodesViewComponent } from './components/nodes-view/nodes-view.component ContainerHistoryComponent, QueueRackComponent, AppsViewComponent, - NodesViewComponent + NodesViewComponent, + ErrorViewComponent ], imports: [ BrowserModule, @@ -95,6 +98,11 @@ import { NodesViewComponent } from './components/nodes-view/nodes-view.component useFactory: envConfigFactory, deps: [EnvconfigService], multi: true + }, + { + provide: HTTP_INTERCEPTORS, + useClass: ApiErrorInterceptor, + multi: true } ], bootstrap: [AppComponent] diff --git a/src/app/components/dashboard/dashboard.component.html b/src/app/components/dashboard/dashboard.component.html index 0a17f40b..e651d053 100644 --- a/src/app/components/dashboard/dashboard.component.html +++ b/src/app/components/dashboard/dashboard.component.html @@ -19,24 +19,24 @@
-
Name
-
{{ clusterInfo.clusterName }}
+
Name
+
{{ clusterInfo.clusterName }}
-
Status
-
{{ clusterInfo.clusterStatus }}
+
Status
+
{{ clusterInfo.clusterStatus }}
-
Nodes
-
{{ clusterInfo.activeNodes }}
+
Nodes
+
{{ clusterInfo.activeNodes }}
-
Applications
-
{{ clusterInfo.runningApplications }}
+
Applications
+
{{ clusterInfo.runningApplications }}
-
Containers
-
{{ clusterInfo.runningContainers }}
+
Containers
+
{{ clusterInfo.runningContainers }}
diff --git a/src/app/components/error-view/error-view.component.html b/src/app/components/error-view/error-view.component.html new file mode 100644 index 00000000..9a66bb1a --- /dev/null +++ b/src/app/components/error-view/error-view.component.html @@ -0,0 +1,21 @@ + + + + Oops, Something went wrong. YuniKorn scheduler is not accessible. + diff --git a/src/app/components/error-view/error-view.component.scss b/src/app/components/error-view/error-view.component.scss new file mode 100644 index 00000000..f64b7a79 --- /dev/null +++ b/src/app/components/error-view/error-view.component.scss @@ -0,0 +1,22 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.error-view { + color: #cb0100; + text-align: center; +} diff --git a/src/app/components/error-view/error-view.component.spec.ts b/src/app/components/error-view/error-view.component.spec.ts new file mode 100644 index 00000000..215491fc --- /dev/null +++ b/src/app/components/error-view/error-view.component.spec.ts @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { configureTestSuite } from 'ng-bullet'; +import { MatCardModule } from '@angular/material'; + +import { ErrorViewComponent } from './error-view.component'; + +describe('ErrorViewComponent', () => { + let component: ErrorViewComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [MatCardModule], + declarations: [ErrorViewComponent] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ErrorViewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create the component', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/error-view/error-view.component.ts b/src/app/components/error-view/error-view.component.ts new file mode 100644 index 00000000..3eae717a --- /dev/null +++ b/src/app/components/error-view/error-view.component.ts @@ -0,0 +1,33 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-error-view', + templateUrl: './error-view.component.html', + styleUrls: ['./error-view.component.scss'] +}) +export class ErrorViewComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/src/app/interceptors/api-error/api-error.interceptor.spec.ts b/src/app/interceptors/api-error/api-error.interceptor.spec.ts new file mode 100644 index 00000000..c2332014 --- /dev/null +++ b/src/app/interceptors/api-error/api-error.interceptor.spec.ts @@ -0,0 +1,42 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TestBed } from '@angular/core/testing'; +import { configureTestSuite } from 'ng-bullet'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { ApiErrorInterceptor } from './api-error.interceptor'; + +describe('ApiErrorInterceptor', () => { + let interceptor: ApiErrorInterceptor; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [RouterTestingModule], + providers: [ApiErrorInterceptor] + }); + }); + + beforeEach(() => { + interceptor = TestBed.get(ApiErrorInterceptor); + }); + + it('should create the interceptor', () => { + expect(interceptor).toBeTruthy(); + }); +}); diff --git a/src/app/interceptors/api-error/api-error.interceptor.ts b/src/app/interceptors/api-error/api-error.interceptor.ts new file mode 100644 index 00000000..30511608 --- /dev/null +++ b/src/app/interceptors/api-error/api-error.interceptor.ts @@ -0,0 +1,43 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Injectable } from '@angular/core'; +import { Router } from '@angular/router'; +import { Observable, throwError } from 'rxjs'; +import { catchError } from 'rxjs/operators'; +import { + HttpEvent, + HttpInterceptor, + HttpHandler, + HttpRequest, + HttpErrorResponse +} from '@angular/common/http'; + +@Injectable() +export class ApiErrorInterceptor implements HttpInterceptor { + constructor(private router: Router) {} + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + return next.handle(request).pipe(catchError(this.handleApiError.bind(this))); + } + + handleApiError(error: HttpErrorResponse) { + this.router.navigate(['/error']); + return throwError(error); + } +} diff --git a/src/styles.scss b/src/styles.scss index 639bfeb9..5c41fbda 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -39,34 +39,6 @@ body { font-weight: 500; } -body { - font-family: 'Roboto', sans-serif; - font-size: 14px; - color: #666; - background: #eee; -} - -.hwx-title { - font-size: 20px; - color: #333; - display: inline-block; -} -.hwx-subtitle { - font-size: 16px; - color: #333; - margin: 5px 0; -} -.hwx-description { - margin: 10px 0; - font-size: 12px; - line-height: 1.432; -} -.hwx-inline-description { - color: #999; - font-size: 12px; - line-height: 1; - margin-bottom: 5px; -} .text-uppercase { text-transform: uppercase; } @@ -75,121 +47,52 @@ body { text-align: center; } -.hwx-error-border { +.error-border { border-color: #ef6162; } -.hwx-warning-border { + +.warning-border { border-color: #e98a40; } -.hwx-success-border { + +.success-border { border-color: #3fae2a; } -.hwx-secondary-border { - border-color: #1eb475; -} -.hwx-external-border { - border-color: #1491c1; -} -.hwx-gray-border { - border-color: #ebecf1; -} -.hwx-error-bg { +.error-bg { background: #ef6162; } -.hwx-warning-bg { + +.warning-bg { background: #e98a40; } -.hwx-success-bg { + +.success-bg { background: #3fae2a; } -.hwx-secondary-bg { - background: #1eb475; -} -.hwx-white-bg { - background: #fff; -} -.hwx-yellow-bg { - background: #ffffdd; -} -.hwx-external-bg { - background: #1491c1; -} -.hwx-tertiary-bg { - background-color: #4db6ac; -} -.hwx-gray-bg { - background-color: #ebecf1; -} -.hwx-error { +.error-text { color: #ef6162; } -.hwx-warning { + +.warning-text { color: #e98a40; } -.hwx-success { + +.success-text { color: #3fae2a; } -.hwx-secondary { - color: #1eb475; -} -.hwx-external { - color: #1491c1; -} -.hwx-gray { - color: #ebecf1; -} -.hwx-tertiary { - color: #4db6ac; -} -.hwx-light { +.light-text { color: #999; } -.hwx-strong { - color: #333; -} -.hwx-regular { - color: #666; -} - -.hwx-tag-o-success, -.hwx-tag-o-green, -.hwx-tag-o-error, -.hwx-tag-o-red, -.hwx-tag-o-warning, -.hwx-tag-o-orange { - -webkit-border-radius: 10px; - -moz-border-radius: 10px; - border-radius: 10px; - padding: 2px 5px; - font-size: 0.7em; - font-weight: 500; -} -.hwx-tag-o-error, -.hwx-tag-o-red { - color: #fff; - background: #ef6162; -} - -.hwx-tag-o-success, -.hwx-tag-o-green { - color: #fff; - background: #3fae2a; -} - -.hwx-tag-o-warning, -.hwx-tag-o-orange { - color: #fff; - background: #e98a40; +.strong-text { + color: #333; } -a, -a:hover { - color: #1491c1; - text-decoration: none; +.regular-text { + color: #666; } strong { @@ -201,12 +104,6 @@ p { margin: 10px 0; } -.hwx-radius-big { - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; -} - .flex-grid { display: flex; }