diff --git a/projects/frontend/data-pipelines/gui/projects/ui/src/app/app.component.css b/projects/frontend/data-pipelines/gui/projects/ui/src/app/app.component.css new file mode 100644 index 0000000000..3cc7f4df03 --- /dev/null +++ b/projects/frontend/data-pipelines/gui/projects/ui/src/app/app.component.css @@ -0,0 +1,4 @@ +/* + * Copyright 2023-2023 VMware, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ diff --git a/projects/frontend/data-pipelines/gui/projects/ui/src/app/app.component.html b/projects/frontend/data-pipelines/gui/projects/ui/src/app/app.component.html new file mode 100644 index 0000000000..bc50b6e176 --- /dev/null +++ b/projects/frontend/data-pipelines/gui/projects/ui/src/app/app.component.html @@ -0,0 +1,78 @@ + + + +
+ +
+ + + +
Logout
+
+
+
+
+ +
+ + + + + Get started + + + + + Explore + + + + + + Data jobs + + + + + + + + + + Manage + + + + Data Jobs + + + + +
+ +
+
+
+ +
+
+

Loading Data Pipelines

+ +
+
+ + diff --git a/projects/frontend/data-pipelines/gui/projects/ui/src/app/app.component.scss b/projects/frontend/data-pipelines/gui/projects/ui/src/app/app.component.scss new file mode 100644 index 0000000000..4ea2edfd64 --- /dev/null +++ b/projects/frontend/data-pipelines/gui/projects/ui/src/app/app.component.scss @@ -0,0 +1,29 @@ +/* + * Copyright 2021-2023 VMware, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +.small-text { + font-size: smaller; +} + +.checking-user { + display: table; + height: 28rem; + width: 100%; + + .loading-spinner { + text-align: center; + display: table-cell; + vertical-align: middle; + + .loading-title { + margin-bottom: .6rem; + } + } +} + +.nav-right { + float: right; + display: contents; +} diff --git a/projects/frontend/data-pipelines/gui/projects/ui/src/app/app.component.spec.ts b/projects/frontend/data-pipelines/gui/projects/ui/src/app/app.component.spec.ts new file mode 100644 index 0000000000..ba271abb96 --- /dev/null +++ b/projects/frontend/data-pipelines/gui/projects/ui/src/app/app.component.spec.ts @@ -0,0 +1,60 @@ +/* + * Copyright 2021-2023 VMware, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { TestBed } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; + +import { Subject } from 'rxjs'; + +import { OAuthService, UrlHelperService } from 'angular-oauth2-oidc'; + +import { NavigationService, RouterService } from '@vdk/shared'; + +import { AppComponent } from './app.component'; +import { TokenResponse } from 'angular-oauth2-oidc/types'; + +describe('AppComponent', () => { + let routerServiceStub: jasmine.SpyObj; + let oAuthServiceStub: jasmine.SpyObj; + let navigationServiceStub: jasmine.SpyObj; + + beforeEach(() => { + routerServiceStub = jasmine.createSpyObj('routerService', ['getState']); + oAuthServiceStub = jasmine.createSpyObj('oAuthService', [ + 'configure', + 'loadDiscoveryDocumentAndLogin', + 'getAccessTokenExpiration', + 'refreshToken', + 'logOut', + 'getIdToken', + 'getIdentityClaims' + ]); + navigationServiceStub = jasmine.createSpyObj('navigationService', ['initialize']); + + routerServiceStub.getState.and.returnValue(new Subject()); + oAuthServiceStub.getIdentityClaims.and.returnValue({}); + oAuthServiceStub.loadDiscoveryDocumentAndLogin.and.returnValue(Promise.resolve(true)); + oAuthServiceStub.getAccessTokenExpiration.and.returnValue(0); + oAuthServiceStub.refreshToken.and.returnValue(Promise.resolve({} as TokenResponse)); + + TestBed.configureTestingModule({ + schemas: [NO_ERRORS_SCHEMA], + declarations: [AppComponent], + imports: [], + providers: [ + UrlHelperService, + { provide: OAuthService, useValue: oAuthServiceStub }, + { provide: NavigationService, useValue: navigationServiceStub }, + { provide: RouterService, useValue: routerServiceStub } + ] + }); + }); + + it('should create the app', () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + }); +}); diff --git a/projects/frontend/data-pipelines/gui/projects/ui/src/app/app.component.ts b/projects/frontend/data-pipelines/gui/projects/ui/src/app/app.component.ts new file mode 100644 index 0000000000..71f93ca121 --- /dev/null +++ b/projects/frontend/data-pipelines/gui/projects/ui/src/app/app.component.ts @@ -0,0 +1,113 @@ +/* + * Copyright 2021-2023 VMware, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Component, OnInit } from '@angular/core'; + +import { timer } from 'rxjs'; + +import { OAuthService } from 'angular-oauth2-oidc'; + +import { NavigationService } from '@vdk/shared'; + +import { authCodeFlowConfig, refreshTokenConfig } from './auth'; + +const REFRESH_TOKEN_START = 500; +const ORG_LINK_ROOT = '/csp/gateway/am/api/orgs/'; +const CONSOLE_CLOUD_URL = 'https://console-stg.cloud.vmware.com/'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'] +}) +export class AppComponent implements OnInit { + title = 'core'; + collapsed = false; + + constructor(private readonly oauthService: OAuthService, + private readonly navigationService: NavigationService) { + this.oauthService + .configure(authCodeFlowConfig); + this.oauthService + .loadDiscoveryDocumentAndLogin() + .then(() => { + this.initTokenRefresh(); + }) + .catch(() => { + // No-op. + }); + } + + logout(): void { + this.oauthService.logOut(); + } + + get idToken(): string { + return this.oauthService.getIdToken(); + } + + get userName(): string { + return this.oauthService.getIdentityClaims() + ? this.getIdentityClaim('username') + : 'N/A'; + } + + /** + * @inheritDoc + */ + ngOnInit(): void { + this.navigationService.initialize(); + } + + private getIdentityClaim(userNamePropName: string): string { + const identityClaims = this.oauthService.getIdentityClaims() as { [key: string]: string }; + + return identityClaims[userNamePropName]; + } + + private initTokenRefresh() { + timer( + REFRESH_TOKEN_START, + AppComponent.toMillis(refreshTokenConfig.refreshTokenCheckInterval)) + .subscribe(() => { + const remainiTimeMillis = this.oauthService.getAccessTokenExpiration() - Date.now(); + if (remainiTimeMillis <= AppComponent.toMillis(refreshTokenConfig.refreshTokenRemainingTime)) { + this.setCustomTokenAttributes(false, null); + this.oauthService + .refreshToken() + .finally(() => { + // No-op. + }); + } + }); + } + + private setCustomTokenAttributes(redirectToConsole: boolean, defaultOrg: { refLink: string }) { + const linkOrgQuery = AppComponent.getOrgLinkFromQueryParams(defaultOrg); + this.oauthService.customQueryParams = { + orgLink: linkOrgQuery, + targetUri: redirectToConsole ? CONSOLE_CLOUD_URL : window.location.href + }; + if (redirectToConsole) { + // Redirect to console cloud because we dont know the tenant url, but console does + this.oauthService.redirectUri = CONSOLE_CLOUD_URL; + } + } + + private static getOrgLinkFromQueryParams(defaultOrg: { refLink: string }): string { + const params = new URLSearchParams(window.location.search); + const orgLinkUnderscored = params.get('org_link'); + const orgLinkBase = params.get('orgLink'); + if (orgLinkBase || orgLinkUnderscored) { + return [orgLinkBase, orgLinkUnderscored].find(el => el); + } else { + return (defaultOrg ? defaultOrg.refLink : ORG_LINK_ROOT); + } + } + + private static toMillis(seconds: number) { + return seconds * 1000; + } +} diff --git a/projects/frontend/data-pipelines/gui/projects/ui/src/app/app.module.ts b/projects/frontend/data-pipelines/gui/projects/ui/src/app/app.module.ts new file mode 100644 index 0000000000..2e8615e52e --- /dev/null +++ b/projects/frontend/data-pipelines/gui/projects/ui/src/app/app.module.ts @@ -0,0 +1,93 @@ +/* + * Copyright 2021-2023 VMware, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { NgModule } from '@angular/core'; +import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; +import { BrowserModule } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; + +import { AuthConfig, OAuthModule, OAuthStorage } from 'angular-oauth2-oidc'; + +import { TimeagoModule } from 'ngx-timeago'; +import { LottieModule } from 'ngx-lottie'; + +import { ApolloModule } from 'apollo-angular'; + +import { ClarityModule } from '@clr/angular'; + +import { VmwComponentsModule } from '@vdk/shared'; + +import { TaurusSharedCoreModule, TaurusSharedFeaturesModule, TaurusSharedNgRxModule } from '@vdk/shared'; + +import { DataPipelinesModule } from '@vdk/data-pipelines'; + +import { AppComponent } from './app.component'; +import { AppRouting } from './app.routing'; +import { authCodeFlowConfig } from './auth'; +import { AuthorizationInterceptor } from './http.interceptor'; +import { GettingStartedComponent } from './getting-started/getting-started.component'; + +// eslint-disable-next-line prefer-arrow/prefer-arrow-functions +export function lottiePlayerLoader() { + return import('lottie-web'); +} + +@NgModule({ + declarations: [AppComponent, GettingStartedComponent], + imports: [ + AppRouting, + BrowserModule, + ClarityModule, + BrowserAnimationsModule, + ApolloModule, + TaurusSharedCoreModule.forRoot(), + TaurusSharedFeaturesModule.forRoot(), + TaurusSharedNgRxModule.forRootWithDevtools(), + TimeagoModule.forRoot(), + LottieModule.forRoot({ player: lottiePlayerLoader }), + VmwComponentsModule.forRoot(), + DataPipelinesModule.forRoot({ + defaultOwnerTeamName: 'taurus', + manageConfig: { + allowKeyTabDownloads: true + }, + exploreConfig: { + showTeamsColumn: true + }, + healthStatusUrl: '/explore/data-jobs?search={0}', + showExecutionsPage: true, + showLineagePage: false + }), + HttpClientModule, + OAuthModule.forRoot({ + resourceServer: { + allowedUrls: [ + 'https://console-stg.cloud.vmware.com/', + 'https://gaz-preview.csp-vidm-prod.com/', + '/data-jobs' + ], + sendAccessToken: true + } + }) + ], + providers: [ + { + provide: OAuthStorage, + useValue: localStorage + }, + { + provide: AuthConfig, + useValue: authCodeFlowConfig + }, + { + provide: HTTP_INTERCEPTORS, + useClass: AuthorizationInterceptor, + multi: true + } + ], + bootstrap: [AppComponent] +}) +export class AppModule { +} diff --git a/projects/frontend/data-pipelines/gui/projects/ui/src/app/app.routing.ts b/projects/frontend/data-pipelines/gui/projects/ui/src/app/app.routing.ts new file mode 100644 index 0000000000..c0601237ad --- /dev/null +++ b/projects/frontend/data-pipelines/gui/projects/ui/src/app/app.routing.ts @@ -0,0 +1,125 @@ +/* + * Copyright 2021-2023 VMware, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +// modules +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { + DataJobDetailsPageComponent, + DataJobExecutionsPageComponent, + DataJobPageComponent, + DataJobsExplorePageComponent, + DataJobsManagePageComponent, + DataPipelinesRoutes +} from '@vdk/data-pipelines'; + +import { GettingStartedComponent } from './getting-started/getting-started.component'; +import { DataPipelinesRoute } from '../../../data-pipelines/src/lib/model'; + +const routes: DataPipelinesRoutes = [ + { path: 'get-started', component: GettingStartedComponent }, + + //Explore + { + path: 'explore/data-jobs', + component: DataJobsExplorePageComponent, + data: { + navigateTo: { + path: '/explore/data-jobs/{0}/{1}', + replacers: [ + { searchValue: '{0}', replaceValue: '$.team' }, + { searchValue: '{1}', replaceValue: '$.job' } + ] + }, + restoreUiWhen: { + previousConfigPathLike: '/explore/data-jobs/:team/:job' + } + } + }, + { + path: 'explore/data-jobs/:team/:job', + component: DataJobPageComponent, + data: { + teamParamKey: 'team', + jobParamKey: 'job', + }, + children: [ + { + path: 'details', + component: DataJobDetailsPageComponent, + data: { + editable: false, + navigateBack: { + path: '/explore/data-jobs' + } + } + }, + { + path: 'executions', + redirectTo: 'details' + }, + { path: '**', redirectTo: 'details' } + ] + }, + + //Manage + { + path: 'manage/data-jobs', + component: DataJobsManagePageComponent, + data: { + navigateTo: { + path: '/manage/data-jobs/{0}/{1}', + replacers: [ + { searchValue: '{0}', replaceValue: '$.team' }, + { searchValue: '{1}', replaceValue: '$.job' } + ] + }, + restoreUiWhen: { + previousConfigPathLike: '/manage/data-jobs/:team/:job' + } + } as DataPipelinesRoute + }, + { + path: 'manage/data-jobs/:team/:job', + component: DataJobPageComponent, + data: { + teamParamKey: 'team', + jobParamKey: 'job', + }, + children: [ + { + path: 'details', + component: DataJobDetailsPageComponent, + data: { + editable: true, + navigateBack: { + path: '/manage/data-jobs' + } + } + }, + { + path: 'executions', + component: DataJobExecutionsPageComponent, + data: { + editable: true, + navigateBack: { + path: '/manage/data-jobs' + } + } + }, + { path: '**', redirectTo: 'details' } + ] + }, + + { path: '**', redirectTo: 'get-started' } +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule] +}) +export class AppRouting { +} diff --git a/projects/frontend/data-pipelines/gui/projects/ui/src/app/auth.ts b/projects/frontend/data-pipelines/gui/projects/ui/src/app/auth.ts new file mode 100644 index 0000000000..1f46077b70 --- /dev/null +++ b/projects/frontend/data-pipelines/gui/projects/ui/src/app/auth.ts @@ -0,0 +1,35 @@ +/* + * Copyright 2021-2023 VMware, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { AuthConfig } from 'angular-oauth2-oidc'; + +export const authCodeFlowConfig: AuthConfig = { + issuer: 'https://console-stg.cloud.vmware.com/csp/gateway/am/api/', + redirectUri: window.location.origin + '/', + skipIssuerCheck: true, + requestAccessToken: true, + oidc: true, + strictDiscoveryDocumentValidation: false, + clientId: 'OqRMdWXWj5niqWsw7sOKvIWEmJqgaUbSHsz', + responseType: 'code', + scope: 'openid ALL_PERMISSIONS customer_number group_names', + showDebugInformation: true, + silentRefreshRedirectUri: window.location.origin + '/silent-refresh.html', + useSilentRefresh: true, // Needed for Code Flow to suggest using iframe-based refreshes + silentRefreshTimeout: 5000, // For faster testing + timeoutFactor: 0.25, // For faster testing + sessionChecksEnabled: true, + clearHashAfterLogin: false, // https://github.com/manfredsteyer/angular-oauth2-oidc/issues/457#issuecomment-431807040, + logoutUrl: 'https://console-stg.cloud.vmware.com/csp/gateway/discovery?logout', + nonceStateSeparator: 'semicolon' // Real semicolon gets mangled by IdentityServer's URI encoding +}; + +export const refreshTokenConfig = { + // Remaining time (in seconds) during which, token refresh will be initalized. This value must be less than the token TTL + refreshTokenRemainingTime: 360, + + // Check interval (in seconds), if token refresh need to be initalized. This value must be less than refreshTokenRemainingTime + refreshTokenCheckInterval: 60 +}; diff --git a/projects/frontend/data-pipelines/gui/projects/ui/src/app/getting-started/getting-started.component.html b/projects/frontend/data-pipelines/gui/projects/ui/src/app/getting-started/getting-started.component.html new file mode 100644 index 0000000000..4d4eb64dfe --- /dev/null +++ b/projects/frontend/data-pipelines/gui/projects/ui/src/app/getting-started/getting-started.component.html @@ -0,0 +1,85 @@ + + +
+
+

Get Started with Data Pipelines

+
+
+

+ Data Pipelines help Data Engineers develop, deploy, run, and manage data processing workloads + This app wraps the data pipelines library in order to provide example of the library usage +

+
+ +
+
+

Learn more recommended

+ +
+
+
+
+

Overview

+

+ High level architecture and capabilities of the data pipelines project +

+
+ +
+
+
+
+
+

How to

+

+ Learn about how to create your first data job +

+
+ +
+
+
+
+
+

Explore

+

+ Explore existing data jobs +

+
+ +
+
+
+ +
+ +
+
+
+ +
+
+
+ +
+
+

Widgets

+
+
+
+ +
+
+
+
diff --git a/projects/frontend/data-pipelines/gui/projects/ui/src/app/getting-started/getting-started.component.scss b/projects/frontend/data-pipelines/gui/projects/ui/src/app/getting-started/getting-started.component.scss new file mode 100644 index 0000000000..674a0ed32a --- /dev/null +++ b/projects/frontend/data-pipelines/gui/projects/ui/src/app/getting-started/getting-started.component.scss @@ -0,0 +1,4 @@ +/* + * Copyright 2021-2023 VMware, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ diff --git a/projects/frontend/data-pipelines/gui/projects/ui/src/app/getting-started/getting-started.component.spec.ts b/projects/frontend/data-pipelines/gui/projects/ui/src/app/getting-started/getting-started.component.spec.ts new file mode 100644 index 0000000000..5619096035 --- /dev/null +++ b/projects/frontend/data-pipelines/gui/projects/ui/src/app/getting-started/getting-started.component.spec.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2021-2023 VMware, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; +import { GettingStartedComponent } from './getting-started.component'; +import { OAuthService } from 'angular-oauth2-oidc'; + +describe('GettingStartedComponent', () => { + let component: GettingStartedComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [GettingStartedComponent], + providers: [OAuthService], + imports: [] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(GettingStartedComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/frontend/data-pipelines/gui/projects/ui/src/app/getting-started/getting-started.component.ts b/projects/frontend/data-pipelines/gui/projects/ui/src/app/getting-started/getting-started.component.ts new file mode 100644 index 0000000000..72f431654d --- /dev/null +++ b/projects/frontend/data-pipelines/gui/projects/ui/src/app/getting-started/getting-started.component.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021-2023 VMware, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-getting-started', + templateUrl: './getting-started.component.html', + styleUrls: ['./getting-started.component.scss'] +}) +export class GettingStartedComponent { + constructor() { + // No-op. + } +} diff --git a/projects/frontend/data-pipelines/gui/projects/ui/src/app/http.interceptor.ts b/projects/frontend/data-pipelines/gui/projects/ui/src/app/http.interceptor.ts new file mode 100644 index 0000000000..2c09e92ae4 --- /dev/null +++ b/projects/frontend/data-pipelines/gui/projects/ui/src/app/http.interceptor.ts @@ -0,0 +1,67 @@ +/* + * Copyright 2021-2023 VMware, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Injectable, Optional } from '@angular/core'; +import { OAuthModuleConfig, OAuthResourceServerErrorHandler, OAuthStorage } from 'angular-oauth2-oidc'; +import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; + +import { Observable } from 'rxjs'; +import { authCodeFlowConfig } from './auth'; + +@Injectable() +export class AuthorizationInterceptor implements HttpInterceptor { + + constructor( + private authStorage: OAuthStorage, + private errorHandler: OAuthResourceServerErrorHandler, + @Optional() private moduleConfig: OAuthModuleConfig) { + } + + /** + * @inheritDoc + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + intercept(req: HttpRequest, next: HttpHandler): Observable> { + const url = req.url.toLowerCase(); + if (!this.moduleConfig) { + return next.handle(req); + } + + if (!this.moduleConfig.resourceServer) { + return next.handle(req); + } + + if (!this.moduleConfig.resourceServer.allowedUrls) { + return next.handle(req); + } + + if (!this.checkUrl(url)) { + return next.handle(req); + } + + const sendAccessToken = this.moduleConfig.resourceServer.sendAccessToken; + + if (sendAccessToken && url.startsWith(authCodeFlowConfig.issuer) && url.endsWith('api/auth/token')) { + const headers = req.headers + .set('Authorization', 'Basic ' + btoa(authCodeFlowConfig.clientId + ':')); + + return next.handle(req.clone({ headers })); + } else if (sendAccessToken) { + const token = this.authStorage.getItem('access_token'); + const header = `Bearer ${token}`; + const headers = req.headers + .set('Authorization', header); + + return next.handle(req.clone({ headers })); + } + + return next.handle(req); + } + + private checkUrl(url: string): boolean { + const found = this.moduleConfig.resourceServer.allowedUrls.find(u => url.startsWith(u)); + return !!found; + } +} diff --git a/projects/frontend/data-pipelines/gui/projects/ui/src/environments/environment.prod.ts b/projects/frontend/data-pipelines/gui/projects/ui/src/environments/environment.prod.ts new file mode 100644 index 0000000000..bdc8e7c053 --- /dev/null +++ b/projects/frontend/data-pipelines/gui/projects/ui/src/environments/environment.prod.ts @@ -0,0 +1,8 @@ +/* + * Copyright 2021-2023 VMware, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +export const environment = { + production: true +}; diff --git a/projects/frontend/data-pipelines/gui/projects/ui/src/environments/environment.ts b/projects/frontend/data-pipelines/gui/projects/ui/src/environments/environment.ts new file mode 100644 index 0000000000..6bf2099149 --- /dev/null +++ b/projects/frontend/data-pipelines/gui/projects/ui/src/environments/environment.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2021-2023 VMware, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +// This file can be replaced during build by using the `fileReplacements` array. +// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. +// The list of file replacements can be found in `angular.json`. + +export const environment = { + production: false +}; + +/* + * For easier debugging in development mode, you can import the following file + * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. + * + * This import should be commented out in production mode because it will have a negative impact + * on performance if an error is thrown. + */ +import 'zone.js/plugins/zone-error'; // Included with Angular CLI. diff --git a/projects/frontend/data-pipelines/gui/projects/ui/src/favicon.ico b/projects/frontend/data-pipelines/gui/projects/ui/src/favicon.ico new file mode 100644 index 0000000000..997406ad22 Binary files /dev/null and b/projects/frontend/data-pipelines/gui/projects/ui/src/favicon.ico differ diff --git a/projects/frontend/data-pipelines/gui/projects/ui/src/index.html b/projects/frontend/data-pipelines/gui/projects/ui/src/index.html new file mode 100644 index 0000000000..ac4328f021 --- /dev/null +++ b/projects/frontend/data-pipelines/gui/projects/ui/src/index.html @@ -0,0 +1,19 @@ + + + + + + + Ui + + + + + + + + + diff --git a/projects/frontend/data-pipelines/gui/projects/ui/src/main.ts b/projects/frontend/data-pipelines/gui/projects/ui/src/main.ts new file mode 100644 index 0000000000..a61616e51e --- /dev/null +++ b/projects/frontend/data-pipelines/gui/projects/ui/src/main.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2021-2023 VMware, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic() + .bootstrapModule(AppModule) + .catch(err => console.error(err)); diff --git a/projects/frontend/data-pipelines/gui/projects/ui/src/polyfills.ts b/projects/frontend/data-pipelines/gui/projects/ui/src/polyfills.ts new file mode 100644 index 0000000000..ebd6d2cbc1 --- /dev/null +++ b/projects/frontend/data-pipelines/gui/projects/ui/src/polyfills.ts @@ -0,0 +1,58 @@ +/* + * Copyright 2021-2023 VMware, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/guide/browser-support + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +import '@webcomponents/custom-elements'; +/** + * By default, zone.js will patch all possible macroTask and DomEvents + * user can disable parts of macroTask/DomEvents patch by setting following flags + * because those flags need to be set before `zone.js` being loaded, and webpack + * will put import in the top of bundle, so user need to create a separate file + * in this directory (for example: zone-flags.ts), and put the following flags + * into that file, and then add the following code before importing zone.js. + * import './zone-flags'; + * + * The flags allowed in zone-flags.ts are listed here. + * + * The following flags will work for all browsers. + * + * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + * + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + * + * (window as any).__Zone_enable_cross_context_check = true; + * + */ +/*************************************************************************************************** + * Zone JS is required by default for Angular itself. + */ +import 'zone.js'; // Included with Angular CLI. +(window as any).global = window; + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ diff --git a/projects/frontend/data-pipelines/gui/projects/ui/src/styles.scss b/projects/frontend/data-pipelines/gui/projects/ui/src/styles.scss new file mode 100644 index 0000000000..43ef083bb1 --- /dev/null +++ b/projects/frontend/data-pipelines/gui/projects/ui/src/styles.scss @@ -0,0 +1,247 @@ +/* + * Copyright 2021-2023 VMware, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +/* You can add global styles to this file, and also import other style files */ + +/** + Placeholder + **/ + +:root { + --taurus-placeholder-color: var(--clr-color-neutral-500, #b3b3b3); +} + +/* _normalize overwrite */ +::-webkit-input-placeholder { + /* clr-forms-placeholder-color */ + color: var(--taurus-placeholder-color) !important; +} + +/* Internet Explorer 10-11 */ +:-ms-input-placeholder { + /* clr-forms-placeholder-color */ + color: var(--taurus-placeholder-color) !important; +} + +/* Microsoft Edge */ +::-ms-input-placeholder { + /* clr-forms-placeholder-color */ + color: var(--taurus-placeholder-color) !important; +} + +/* Chrome, Firefox, Opera, Safari 10.1+ */ +::placeholder { + /* clr-forms-placeholder-color */ + color: var(--taurus-placeholder-color) !important; +} + +.fade-to-dark.dark { + --taurus-placeholder-color: #b3b3b3; +} + +html { + scroll-behavior: smooth; +} + +.content-container.switch-btn-bottom .nav-content { + margin-bottom: 1.5rem; +} + +/* combo box */ +.clr-popover-content { + margin-top: 10px; +} + +/* vmw search component */ +vmw-search .search-container cds-icon[shape=search] { + top: 2px !important; +} + +/* forms */ +.destination-modal-body .custom-header-item .custom-header-input { + display: inline-block; + max-width: 10rem; +} + +.modal-submit { + position: absolute; + left: -9999px; + z-index: -9999; + visibility: hidden; +} + +vmw-form-section-container.p-header-section .form-header { + padding-bottom: 0 !important; + margin-top: 0; +} + +vmw-form-section-container.p-header-section .form-header .section-title { + font-size: .7rem; + font-weight: 500; +} + +vmw-form-section-container.p-header-section .vmw-vertical-line { + margin-top: 0 !important; +} + +vmw-form-section-container.p-header-section .csp-edit-button { + margin-left: 1rem !important; +} + +/* links */ +a.disabled { + pointer-events: none; + cursor: default; + opacity: 0.5; +} + +/* theme */ +.toogle-theme { + position: absolute; + bottom: 0; + background-color: transparent !important; + padding-left: 0 !important; +} + +.toogle-theme:hover { + background-color: transparent !important; +} + +.toogle-theme .nav-link { + background-color: transparent !important; +} + +.page-title { + margin-top: 0; +} + +.tc { + text-align: center !important; +} + +/* margins */ +.m-0 { + margin: 0 !important; +} + +.mt-0 { + margin-top: 0 !important; +} + +.mb-12 { + margin-bottom: 12px !important; +} + +.pl-35 { + padding-left: 35px !important; +} + +.w-100 { + width: 100% !important; +} + +/* modals */ +.modal-body > :first-child { + margin-top: 0; +} + +input.modal-form-control { + width: 12rem; +} + +select.modal-form-control { + width: 8rem; +} + +.modal-actions { + margin-top: -20px !important; +} + +/* color styles */ +.red { + color: #EB5757; +} + +/* position */ +.right { + float: right; +} + +.left { + float: left; +} + +/* lists */ +ul.idented { + text-indent: 2em; +} + +/* media */ +@media (max-width: 600px) { + .right { + float: none !important; + } +} + +/* datagrid cells */ +.cell-img-container { + min-width: 30px !important; + width: 60px !important; + text-align: center !important; +} + +.cell-center { + margin: auto !important; +} + +.enabled-column { + width: 6rem !important; +} + +/* limit-height */ +.limit-height { + height: 50vh; +} + +.limit-height clr-datagrid { + height: 50vh !important; +} + +.limit-height clr-datagrid .datagrid { + margin-top: 0; +} + +.limit-height clr-datagrid clr-dg-cell { + margin: auto; +} + +/* team-list-component */ +.team-list-container .clr-form-control { + margin-top: 0 !important; +} + +/* CSP-HEADER */ +/* Hide header assets */ +.header .header-actions .nav-icon.app-switcher { + width: 0; +} + +.header .header-actions .nav-icon.app-switcher cds-icon { + display: none !important; +} + +.header .header-actions .nav-icon.user-menu span.org-name { + display: none; +} + +@media (min-width: 768px) { + .header .header-actions .nav-icon.user-menu div { + margin-top: .5rem; + } +} + +.cdk-overlay-container { + z-index: 1100; +} diff --git a/projects/frontend/data-pipelines/gui/projects/ui/src/test.ts b/projects/frontend/data-pipelines/gui/projects/ui/src/test.ts new file mode 100644 index 0000000000..d57e73ade7 --- /dev/null +++ b/projects/frontend/data-pipelines/gui/projects/ui/src/test.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2021-2023 VMware, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/testing'; +import { getTestBed } from '@angular/core/testing'; +import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; + +declare const require: { + context(path: string, deep?: boolean, filter?: RegExp): { + keys(): string[]; + (id: string): T; + }; +}; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting(), + { + teardown: { destroyAfterEach: false } + } +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context);