diff options
Diffstat (limited to 'webapp/src/app/pages')
46 files changed, 2192 insertions, 0 deletions
diff --git a/webapp/src/app/pages/build/build.component.html b/webapp/src/app/pages/build/build.component.html new file mode 100644 index 0000000..a1ef62d --- /dev/null +++ b/webapp/src/app/pages/build/build.component.html @@ -0,0 +1,63 @@ +<div class="row"> + <div class="col-12"> + <nb-card-body> + <nb-actions size="medium"> + <nb-action> + <xds-project-select-dropdown></xds-project-select-dropdown> + </nb-action> + <nb-action> + <xds-sdk-select-dropdown></xds-sdk-select-dropdown> + </nb-action> + </nb-actions> + </nb-card-body> + </div> + <div class="col-md-12 col-lg-12 col-xxxl-6"> + <nb-card size="xlarge"> + <nb-tabset fullWidth> + + <nb-tab tabTitle="Build"> + + <div class="row" style="margin-top:1em;"> + <!-- FIXME SEB + <button class="btn pull-right " (click)="reset() "> + <span class="fa fa-eraser fa-size-x2"></span> + </button> + --> + <div class="col-md-12 text-center "> + <textarea rows="20" class="textarea-scroll" #scrollOutput>{{ cmdOutput }}</textarea> + </div> + </div> + + <nb-card-body> + <nb-actions size="medium" fullWidth> + <nb-action (click)="clean()"> + <i class="fa fa-eraser"></i> + <span>Clean</span> + </nb-action> + <nb-action (click)="preBuild()"> + <i class="nb-list"></i> + <span>Pre-Build</span> + </nb-action> + <nb-action (click)="build()"> + <i class="fa fa-wrench"></i> + <span>Build</span> + </nb-action> + <nb-action (click)="populate()"> + <i class="fa fa-send"></i> + <span>Populate</span> + </nb-action> + </nb-actions> + </nb-card-body> + + </nb-tab> + + <nb-tab tabTitle="Deploy"> + <span> Content deploy...</span> + </nb-tab> + <nb-tab tabTitle="Debug"> + <span> Content debug...</span> + </nb-tab> + </nb-tabset> + </nb-card> + </div> +</div> diff --git a/webapp/src/app/pages/build/build.component.scss b/webapp/src/app/pages/build/build.component.scss new file mode 100644 index 0000000..b256f66 --- /dev/null +++ b/webapp/src/app/pages/build/build.component.scss @@ -0,0 +1,37 @@ +@import '../../@theme/styles/themes'; +@import '~bootstrap/scss/mixins/breakpoints'; +@import '~@nebular/theme/styles/global/bootstrap/breakpoints'; +@include nb-install-component() { + nb-tabset { + height: 100%; + display: flex; + flex-direction: column; + } + nb-tab { + padding: nb-theme(padding); + } + /deep/ ngx-tab1, + /deep/ ngx-tab2 { + display: block; + padding: nb-theme(padding); + } + @include media-breakpoint-down(xs) { + nb-tabset /deep/ul { + font-size: 1rem; + padding: 0 0.25rem; + } + } +} + +nb-action { + i { + font-size: 2rem; + margin-right: 0.5rem; + } +} + +.textarea-scroll { + border-color: lightgray; + width: 97%; + overflow-y: scroll; +} diff --git a/webapp/src/app/pages/build/build.component.spec.ts b/webapp/src/app/pages/build/build.component.spec.ts new file mode 100644 index 0000000..016192c --- /dev/null +++ b/webapp/src/app/pages/build/build.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BuildComponent } from './build.component'; + +describe('BuildComponent', () => { + let component: BuildComponent; + let fixture: ComponentFixture<BuildComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ BuildComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(BuildComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/webapp/src/app/pages/build/build.component.ts b/webapp/src/app/pages/build/build.component.ts new file mode 100644 index 0000000..5adb9bc --- /dev/null +++ b/webapp/src/app/pages/build/build.component.ts @@ -0,0 +1,198 @@ +import { Component, ViewEncapsulation, AfterViewChecked, ElementRef, ViewChild, OnInit, Input } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import { FormControl, FormGroup, Validators, FormBuilder } from '@angular/forms'; +import { CookieService } from 'ngx-cookie'; + +import 'rxjs/add/operator/scan'; +import 'rxjs/add/operator/startWith'; + +import { XDSAgentService, ICmdOutput } from '../../@core-xds/services/xdsagent.service'; +import { ProjectService, IProject } from '../../@core-xds/services/project.service'; +import { AlertService, IAlert } from '../../@core-xds/services/alert.service'; +import { SdkService } from '../../@core-xds/services/sdk.service'; + +@Component({ + selector: 'xds-panel-build', + templateUrl: './build.component.html', + styleUrls: ['./build.component.scss'], + encapsulation: ViewEncapsulation.None, +}) + +export class BuildComponent implements OnInit, AfterViewChecked { + @ViewChild('scrollOutput') private scrollContainer: ElementRef; + + // FIXME workaround of https://github.com/angular/angular-cli/issues/2034 + // should be removed with angular 5 + // @Input() curProject: IProject; + @Input() curProject = <IProject>null; + + public buildForm: FormGroup; + public subpathCtrl = new FormControl('', Validators.required); + public debugEnable = false; + public buildIsCollapsed = false; + public cmdOutput: string; + public cmdInfo: string; + + private startTime: Map<string, number> = new Map<string, number>(); + + constructor( + private prjSvr: ProjectService, + private xdsSvr: XDSAgentService, + private fb: FormBuilder, + private alertSvr: AlertService, + private sdkSvr: SdkService, + private cookie: CookieService, + ) { + this.cmdOutput = ''; + this.cmdInfo = ''; // TODO: to be remove (only for debug) + this.buildForm = fb.group({ + subpath: this.subpathCtrl, + cmdClean: ['', Validators.nullValidator], + cmdPrebuild: ['', Validators.nullValidator], + cmdBuild: ['', Validators.nullValidator], + cmdPopulate: ['', Validators.nullValidator], + cmdArgs: ['', Validators.nullValidator], + envVars: ['', Validators.nullValidator], + }); + } + + ngOnInit() { + // Set default settings + // TODO save & restore values from cookies + this.buildForm.patchValue({ + subpath: '', + cmdClean: 'rm -rf build', + cmdPrebuild: 'mkdir -p build && cd build && cmake ..', + cmdBuild: 'cd build && make', + cmdPopulate: 'cd build && make remote-target-populate', + cmdArgs: '', + envVars: '', + }); + + // Command output data tunneling + this.xdsSvr.CmdOutput$.subscribe(data => { + this.cmdOutput += data.stdout; + this.cmdOutput += data.stderr; + }); + + // Command exit + this.xdsSvr.CmdExit$.subscribe(exit => { + if (this.startTime.has(exit.cmdID)) { + this.cmdInfo = 'Last command duration: ' + this._computeTime(this.startTime.get(exit.cmdID)); + this.startTime.delete(exit.cmdID); + } + + if (exit && exit.code !== 0) { + this.cmdOutput += '--- Command exited with code ' + exit.code + ' ---\n\n'; + } + }); + + this._scrollToBottom(); + + // only use for debug + this.debugEnable = (this.cookie.get('debug_build') === '1'); + } + + ngAfterViewChecked() { + this._scrollToBottom(); + } + + reset() { + this.cmdOutput = ''; + } + + clean() { + this._exec( + this.buildForm.value.cmdClean, + this.buildForm.value.subpath, + [], + this.buildForm.value.envVars); + } + + preBuild() { + this._exec( + this.buildForm.value.cmdPrebuild, + this.buildForm.value.subpath, + [], + this.buildForm.value.envVars); + } + + build() { + this._exec( + this.buildForm.value.cmdBuild, + this.buildForm.value.subpath, + [], + this.buildForm.value.envVars + ); + } + + populate() { + this._exec( + this.buildForm.value.cmdPopulate, + this.buildForm.value.subpath, + [], // args + this.buildForm.value.envVars + ); + } + + execCmd() { + this._exec( + this.buildForm.value.cmdArgs, + this.buildForm.value.subpath, + [], + this.buildForm.value.envVars + ); + } + + private _exec(cmd: string, dir: string, args: string[], env: string) { + if (!this.curProject) { + this.alertSvr.warning('No active project', true); + } + + // const prjID = this.curProject.id; + const prjID = this.prjSvr.getCurrent().id; + + this.cmdOutput += this._outputHeader(); + + const sdkid = this.sdkSvr.getCurrentId(); + + // Detect key=value in env string to build array of string + const envArr = []; + env.split(';').forEach(v => envArr.push(v.trim())); + + const t0 = performance.now(); + this.cmdInfo = 'Start build of ' + prjID + ' at ' + t0; + + this.xdsSvr.exec(prjID, dir, cmd, sdkid, args, envArr) + .subscribe(res => { + this.startTime.set(String(res.cmdID), t0); + }, + err => { + this.cmdInfo = 'Last command duration: ' + this._computeTime(t0); + this.alertSvr.error('ERROR: ' + err); + }); + } + + private _scrollToBottom(): void { + try { + this.scrollContainer.nativeElement.scrollTop = this.scrollContainer.nativeElement.scrollHeight; + } catch (err) { } + } + + private _computeTime(t0: number, t1?: number): string { + const enlap = Math.round((t1 || performance.now()) - t0); + if (enlap < 1000.0) { + return enlap.toFixed(2) + ' ms'; + } else { + return (enlap / 1000.0).toFixed(3) + ' seconds'; + } + } + + private _outputHeader(): string { + return '--- ' + new Date().toString() + ' ---\n'; + } + + private _outputFooter(): string { + return '\n'; + } +} diff --git a/webapp/src/app/pages/build/build.module.ts b/webapp/src/app/pages/build/build.module.ts new file mode 100644 index 0000000..ac1dfab --- /dev/null +++ b/webapp/src/app/pages/build/build.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { ThemeModule } from '../../@theme/theme.module'; + +import { BuildComponent } from './build.component'; +import { ProjectSelectDropdownComponent } from './settings/project-select-dropdown.component'; +import { SdkSelectDropdownComponent } from './settings/sdk-select-dropdown.component'; + +@NgModule({ + imports: [ + ThemeModule, + ], + declarations: [ + BuildComponent, + ProjectSelectDropdownComponent, + SdkSelectDropdownComponent, + ], + entryComponents: [ + ], +}) +export class BuildModule { } diff --git a/webapp/src/app/pages/build/settings/project-select-dropdown.component.ts b/webapp/src/app/pages/build/settings/project-select-dropdown.component.ts new file mode 100644 index 0000000..da3580a --- /dev/null +++ b/webapp/src/app/pages/build/settings/project-select-dropdown.component.ts @@ -0,0 +1,40 @@ +import { Component, OnInit, Input } from '@angular/core'; + +import { IProject, ProjectService } from '../../../@core-xds/services/project.service'; + +@Component({ + selector: 'xds-project-select-dropdown', + template: ` + <div class="form-group"> + <label>Project</label> + <select class="form-control"> + <option *ngFor="let prj of projects" (click)="select(prj)">{{prj.label}}</option> + </select> + </div> + `, +}) +export class ProjectSelectDropdownComponent implements OnInit { + + projects: IProject[]; + curPrj: IProject; + + constructor(private prjSvr: ProjectService) { } + + ngOnInit() { + this.curPrj = this.prjSvr.getCurrent(); + this.prjSvr.Projects$.subscribe((s) => { + if (s) { + this.projects = s; + if (this.curPrj === null || s.indexOf(this.curPrj) === -1) { + this.prjSvr.setCurrent(this.curPrj = s.length ? s[0] : null); + } + } + }); + } + + select(s) { + this.prjSvr.setCurrent(this.curPrj = s); + } +} + + diff --git a/webapp/src/app/pages/build/settings/sdk-select-dropdown.component.ts b/webapp/src/app/pages/build/settings/sdk-select-dropdown.component.ts new file mode 100644 index 0000000..562386d --- /dev/null +++ b/webapp/src/app/pages/build/settings/sdk-select-dropdown.component.ts @@ -0,0 +1,44 @@ +import { Component, OnInit, Input } from '@angular/core'; + +import { ISdk, SdkService } from '../../../@core-xds/services/sdk.service'; + +@Component({ + selector: 'xds-sdk-select-dropdown', + template: ` + <div class="form-group"> + <label>SDK</label> + <select class="form-control"> + <option *ngFor="let sdk of sdks" (click)="select(sdk)">{{sdk.name}}</option> + </select> + </div> + `, +}) +export class SdkSelectDropdownComponent implements OnInit { + + // FIXME investigate to understand why not working with sdks as input + // <xds-sdk-select-dropdown [sdks]="(sdks$ | async)"></xds-sdk-select-dropdown> + // @Input() sdks: ISdk[]; + sdks: ISdk[]; + + curSdk: ISdk; + + constructor(private sdkSvr: SdkService) { } + + ngOnInit() { + this.curSdk = this.sdkSvr.getCurrent(); + this.sdkSvr.Sdks$.subscribe((s) => { + if (s) { + this.sdks = s; + if (this.curSdk === null || s.indexOf(this.curSdk) === -1) { + this.sdkSvr.setCurrent(this.curSdk = s.length ? s[0] : null); + } + } + }); + } + + select(s) { + this.sdkSvr.setCurrent(this.curSdk = s); + } +} + + diff --git a/webapp/src/app/pages/config/config-global/config-global.component.html b/webapp/src/app/pages/config/config-global/config-global.component.html new file mode 100644 index 0000000..0038510 --- /dev/null +++ b/webapp/src/app/pages/config/config-global/config-global.component.html @@ -0,0 +1,37 @@ +<div class="row"> + <div class="col-md-12"> + <nb-card> + <nb-card-header>Global Configuration</nb-card-header> + <nb-card-body> + + <form (ngSubmit)="onSubmit()" #ConfigGlobalForm="ngForm"> + <div class="form-group row"> + <label class="col-sm-3 col-form-label">Language</label> + <div class="col-sm-9"> + <select class="form-control" (ngModelChange)="configFormChanged=true"> + <option>English</option> + <!-- FIXME: implement i18n and add | translate + <option>French</option> --> + </select> + </div> + </div> + + <div class="form-group row"> + <label class="col-sm-3 col-form-label">Theme</label> + <div class="col-sm-9"> + <ngx-theme-switcher id="theme-switcher"></ngx-theme-switcher> + </div> + </div> + + <!-- + <div class="form-group row"> + <div class="offset-sm-3 col-sm-9"> + <button type="submit" class="btn btn-primary" [disabled]="!configFormChanged">Apply</button> + </div> + </div> + --> + </form> + </nb-card-body> + </nb-card> + </div> +</div> diff --git a/webapp/src/app/pages/config/config-global/config-global.component.scss b/webapp/src/app/pages/config/config-global/config-global.component.scss new file mode 100644 index 0000000..955c54e --- /dev/null +++ b/webapp/src/app/pages/config/config-global/config-global.component.scss @@ -0,0 +1,20 @@ +.full-width { + flex: 1; + min-width: 220px; +} + +nb-checkbox { + margin-bottom: 1rem; +} + +.form-inline > * { + margin: 0 1.5rem 1.5rem 0; +} + +nb-card.inline-form-card nb-card-body { + padding-bottom: 0; +} + +#theme-switcher { + align-items: left; +} diff --git a/webapp/src/app/pages/config/config-global/config-global.component.ts b/webapp/src/app/pages/config/config-global/config-global.component.ts new file mode 100644 index 0000000..fcd8b62 --- /dev/null +++ b/webapp/src/app/pages/config/config-global/config-global.component.ts @@ -0,0 +1,25 @@ +import { Component, OnInit } from '@angular/core'; + +import { ConfigService, IConfig } from '../../../@core-xds/services/config.service'; + +@Component({ + selector: 'xds-config-global', + styleUrls: ['./config-global.component.scss'], + templateUrl: './config-global.component.html', +}) +export class ConfigGlobalComponent implements OnInit { + + public configFormChanged = false; + + constructor( + private configSvr: ConfigService, + ) { + } + + ngOnInit() { + } + + onSubmit() { + } +} + diff --git a/webapp/src/app/pages/config/config-routing.module.ts b/webapp/src/app/pages/config/config-routing.module.ts new file mode 100644 index 0000000..4e7cf27 --- /dev/null +++ b/webapp/src/app/pages/config/config-routing.module.ts @@ -0,0 +1,32 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { ConfigComponent } from './config.component'; +import { ConfigGlobalComponent } from './config-global/config-global.component'; +import { ConfigXdsComponent } from './config-xds/config-xds.component'; + +const routes: Routes = [{ + path: '', + component: ConfigComponent, + children: [ + { + path: 'global', + component: ConfigGlobalComponent, + }, { + path: 'xds', + component: ConfigXdsComponent, + }, + ], +}]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class ConfigRoutingModule { } + +export const routedConfig = [ + ConfigComponent, + ConfigGlobalComponent, + ConfigXdsComponent, +]; diff --git a/webapp/src/app/pages/config/config-xds/config-xds.component.html b/webapp/src/app/pages/config/config-xds/config-xds.component.html new file mode 100644 index 0000000..31559e2 --- /dev/null +++ b/webapp/src/app/pages/config/config-xds/config-xds.component.html @@ -0,0 +1,34 @@ +<div class="row"> + <div class="col-md-12"> + <nb-card> + <nb-card-header>XDS Server Configuration</nb-card-header> + <nb-card-body> + <form (ngSubmit)="onSubmit()" #ConfigXdsForm="ngForm"> + <div class="form-group row"> + <label class="col-sm-3 col-form-label">XDS Server URL</label> + <div class="col-sm-8"> + <input type="url" class="form-control" [ngClass]="{ 'form-control-danger': !server.connected }" id="inputServerUrl" [(ngModel)]="xdsServerUrl" name="serverUrl" (ngModelChange)="configFormChanged=true" [disabled]="connecting"> + </div> + <div class="col-sm-1"> + <span *ngIf="!connecting" class="fa fa-fw fa-exchange fa-size-x2 vcenter" [style.color]="server.connected?'green':'red'"></span> + <span *ngIf="connecting" class="fa fa-gear faa-spin animated fa-size-x2 vcenter"></span> + </div> + </div> + <div class="form-group row"> + <label class="col-sm-3 col-form-label">XDS Server connection retry</label> + <div class="col-sm-8"> + <input type="number" class="form-control" id="inputServerConnRetry" [(ngModel)]="server.connRetry" name="serverRetry" (ngModelChange)="configFormChanged=true"> + </div> + </div> + + <div class="form-group row"> + <div class="offset-sm-3 col-sm-9"> + <button type="submit" class="btn btn-primary" [disabled]=" + connecting || (server.connected && !configFormChanged)">Apply</button> + </div> + </div> + </form> + </nb-card-body> + </nb-card> + </div> +</div> diff --git a/webapp/src/app/pages/config/config-xds/config-xds.component.scss b/webapp/src/app/pages/config/config-xds/config-xds.component.scss new file mode 100644 index 0000000..d7571b9 --- /dev/null +++ b/webapp/src/app/pages/config/config-xds/config-xds.component.scss @@ -0,0 +1,26 @@ +.full-width { + flex: 1; + min-width: 220px; +} + +nb-checkbox { + margin-bottom: 1rem; +} + +.form-inline > * { + margin: 0 1.5rem 1.5rem 0; +} + +nb-card.inline-form-card nb-card-body { + padding-bottom: 0; +} + +.fa-size-x2 { + font-size: 20px; +} + +.vcenter { + //display: inline-block; + //vertical-align: middle; + margin-top: 33%; +} diff --git a/webapp/src/app/pages/config/config-xds/config-xds.component.ts b/webapp/src/app/pages/config/config-xds/config-xds.component.ts new file mode 100644 index 0000000..ffd236d --- /dev/null +++ b/webapp/src/app/pages/config/config-xds/config-xds.component.ts @@ -0,0 +1,63 @@ +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; + +import { XDSConfigService } from '../../../@core-xds/services/xds-config.service'; +import { IXDServerCfg } from '../../../@core-xds/services/xdsagent.service'; +import { AlertService, IAlert } from '../../../@core-xds/services/alert.service'; +import { NotificationsComponent } from '../../notifications/notifications.component'; + +// Import RxJs required methods +import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/catch'; + +@Component({ + selector: 'xds-config-xds', + styleUrls: ['./config-xds.component.scss'], + templateUrl: './config-xds.component.html', +}) +export class ConfigXdsComponent implements OnInit { + + // TODO: cleanup agentStatus$: Observable<IAgentStatus>; + connecting = false; + xdsServerUrl = ''; + server: IXDServerCfg; + + configFormChanged = false; + + constructor( + private XdsConfigSvr: XDSConfigService, + private alert: AlertService, + ) { + } + + ngOnInit() { + // FIXME support multiple servers + + this.server = this.XdsConfigSvr.getCurServer(); + this.xdsServerUrl = this.server.url; + + this.XdsConfigSvr.onCurServer().subscribe(svr => { + this.xdsServerUrl = svr.url; + this.server = svr; + }); + } + + onSubmit() { + if (!this.configFormChanged && this.server.connected) { + return; + } + this.configFormChanged = false; + this.connecting = true; + this.server.url = this.xdsServerUrl; + this.XdsConfigSvr.setCurServer(this.server) + .subscribe(cfg => { + this.connecting = false; + }, + err => { + this.connecting = false; + this.alert.error(err); + }); + } + +} + diff --git a/webapp/src/app/pages/config/config-xds/downloadXdsAgent.component.ts b/webapp/src/app/pages/config/config-xds/downloadXdsAgent.component.ts new file mode 100644 index 0000000..3901331 --- /dev/null +++ b/webapp/src/app/pages/config/config-xds/downloadXdsAgent.component.ts @@ -0,0 +1,35 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'xds-dwnl-agent', + template: ` + <template #popTemplate> + <h3>Install xds-agent:</h3> + <ul> + <li>On Linux machine <a href="{{url_OS_Linux}}" target="_blank"> + <span class="fa fa-external-link"></span></a></li> + + <li>On Windows machine <a href="{{url_OS_Other}}" target="_blank"><span class="fa fa-external-link"></span></a></li> + + <li>On MacOS machine <a href="{{url_OS_Other}}" target="_blank"><span class="fa fa-external-link"></span></a></li> + </ul> + <button type="button" class="btn btn-sm" (click)="pop.hide()"> Cancel </button> + </template> + <button type="button" class="btn btn-link fa fa-download fa-size-x2" + [popover]="popTemplate" + #pop="bs-popover" + placement="left"> + </button> + `, + styles: [` + .fa-size-x2 { + font-size: 20px; + } + `] +}) + +export class DwnlAgentComponent { + + public url_OS_Linux = 'https://en.opensuse.org/LinuxAutomotive#Installation_AGL_XDS'; + public url_OS_Other = 'https://github.com/iotbzh/xds-agent#how-to-install-on-other-platform'; +} diff --git a/webapp/src/app/pages/config/config.component.ts b/webapp/src/app/pages/config/config.component.ts new file mode 100644 index 0000000..f52cab0 --- /dev/null +++ b/webapp/src/app/pages/config/config.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'xds-config', + template: ` + <router-outlet></router-outlet> + `, +}) +export class ConfigComponent { +} diff --git a/webapp/src/app/pages/config/config.module.ts b/webapp/src/app/pages/config/config.module.ts new file mode 100644 index 0000000..2fdaf94 --- /dev/null +++ b/webapp/src/app/pages/config/config.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from '@angular/core'; + +import { ThemeModule } from '../../@theme/theme.module'; +import { ConfigRoutingModule, routedConfig } from './config-routing.module'; + +@NgModule({ + imports: [ + ThemeModule, + ConfigRoutingModule, + ], + declarations: [ + ...routedConfig, + ] +}) +export class ConfigModule { } diff --git a/webapp/src/app/pages/dashboard/dashboard.component.html b/webapp/src/app/pages/dashboard/dashboard.component.html new file mode 100644 index 0000000..9160019 --- /dev/null +++ b/webapp/src/app/pages/dashboard/dashboard.component.html @@ -0,0 +1,10 @@ +<div class="row"> + Dashboard page... + <!-- + <div class="col-xxxl-3 col-md-6"> + <ngx-status-card title="Light" type="primary"> + <i class="nb-lightbulb"></i> + </ngx-status-card> + </div> +--> +</div> diff --git a/webapp/src/app/pages/dashboard/dashboard.component.scss b/webapp/src/app/pages/dashboard/dashboard.component.scss new file mode 100644 index 0000000..6f1f0e0 --- /dev/null +++ b/webapp/src/app/pages/dashboard/dashboard.component.scss @@ -0,0 +1,16 @@ +@import '../../@theme/styles/themes'; +@import '~bootstrap/scss/mixins/breakpoints'; +@import '~@nebular/theme/styles/global/bootstrap/breakpoints'; + +@include nb-install-component() { + .solar-card nb-card-header { + border: none; + padding-bottom: 0; + } + + @include media-breakpoint-down(is) { + /deep/ nb-card.large-card { + height: nb-theme(card-height-medium); + } + } +} diff --git a/webapp/src/app/pages/dashboard/dashboard.component.ts b/webapp/src/app/pages/dashboard/dashboard.component.ts new file mode 100644 index 0000000..a4539cb --- /dev/null +++ b/webapp/src/app/pages/dashboard/dashboard.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-dashboard', + styleUrls: ['./dashboard.component.scss'], + templateUrl: './dashboard.component.html', +}) +export class DashboardComponent { +} diff --git a/webapp/src/app/pages/dashboard/dashboard.module.ts b/webapp/src/app/pages/dashboard/dashboard.module.ts new file mode 100644 index 0000000..725df7a --- /dev/null +++ b/webapp/src/app/pages/dashboard/dashboard.module.ts @@ -0,0 +1,52 @@ +import { NgModule } from '@angular/core'; +import { AngularEchartsModule } from 'ngx-echarts'; + +import { ThemeModule } from '../../@theme/theme.module'; +import { DashboardComponent } from './dashboard.component'; +import { StatusCardComponent } from './status-card/status-card.component'; +/* +import { ContactsComponent } from './contacts/contacts.component'; +import { RoomsComponent } from './rooms/rooms.component'; +import { RoomSelectorComponent } from './rooms/room-selector/room-selector.component'; +import { TemperatureComponent } from './temperature/temperature.component'; +import { TemperatureDraggerComponent } from './temperature/temperature-dragger/temperature-dragger.component'; +import { TeamComponent } from './team/team.component'; +import { KittenComponent } from './kitten/kitten.component'; +import { SecurityCamerasComponent } from './security-cameras/security-cameras.component'; +import { ElectricityComponent } from './electricity/electricity.component'; +import { ElectricityChartComponent } from './electricity/electricity-chart/electricity-chart.component'; +import { WeatherComponent } from './weather/weather.component'; +import { SolarComponent } from './solar/solar.component'; +import { PlayerComponent } from './rooms/player/player.component'; +import { TrafficComponent } from './traffic/traffic.component'; +import { TrafficChartComponent } from './traffic/traffic-chart.component'; +*/ + +@NgModule({ + imports: [ + ThemeModule, + AngularEchartsModule, + ], + declarations: [ + DashboardComponent, + StatusCardComponent, + /* + TemperatureDraggerComponent, + ContactsComponent, + RoomSelectorComponent, + TemperatureComponent, + RoomsComponent, + TeamComponent, + KittenComponent, + SecurityCamerasComponent, + ElectricityComponent, + ElectricityChartComponent, + WeatherComponent, + PlayerComponent, + SolarComponent, + TrafficComponent, + TrafficChartComponent, + */ + ], +}) +export class DashboardModule { } diff --git a/webapp/src/app/pages/dashboard/status-card/status-card.component.scss b/webapp/src/app/pages/dashboard/status-card/status-card.component.scss new file mode 100644 index 0000000..08abc61 --- /dev/null +++ b/webapp/src/app/pages/dashboard/status-card/status-card.component.scss @@ -0,0 +1,142 @@ +@import '../../../@theme/styles/themes'; +@import '~@nebular/theme/styles/global/bootstrap/hero-buttons'; + +@include nb-install-component() { + nb-card { + flex-direction: row; + align-items: center; + height: 6rem; + overflow: visible; + + $bevel: btn-hero-bevel(nb-theme(card-bg)); + $shadow: nb-theme(btn-hero-shadow); + box-shadow: $bevel, $shadow; + + .icon-container { + height: 100%; + padding: 0.625rem; + } + + .icon { + display: flex; + align-items: center; + justify-content: center; + width: 5.75rem; + height: 4.75rem; + font-size: 3.75rem; + border-radius: nb-theme(card-border-radius); + transition: width 0.4s ease; + transform: translate3d(0, 0, 0); + -webkit-transform-style: preserve-3d; + -webkit-backface-visibility: hidden; + color: nb-theme(color-white); + + &.primary { + @include btn-hero-primary-gradient(); + @include btn-hero-primary-bevel-glow-shadow(); + } + &.success { + @include btn-hero-success-gradient(); + @include btn-hero-success-bevel-glow-shadow(); + } + &.info { + @include btn-hero-info-gradient(); + @include btn-hero-info-bevel-glow-shadow(); + } + &.warning { + @include btn-hero-warning-gradient(); + @include btn-hero-warning-bevel-glow-shadow(); + } + } + + &:hover { + background: lighten(nb-theme(card-bg), 5%); + + .icon { + &.primary { + background-image: btn-hero-primary-light-gradient(); + } + &.success { + background-image: btn-hero-success-light-gradient(); + } + &.info { + background-image: btn-hero-info-light-gradient(); + } + &.warning { + background-image: btn-hero-warning-light-gradient(); + } + } + } + + &.off { + color: nb-theme(card-fg); + + .icon { + color: nb-theme(card-fg); + + &.primary, &.success, &.info, &.warning { + box-shadow: none; + background-image: linear-gradient(to right, transparent, transparent); + } + } + + .title { + color: nb-theme(card-fg); + } + } + + .details { + display: flex; + flex-direction: column; + justify-content: center; + height: 100%; + padding: 0 0.5rem 0 0.75rem; + border-left: 1px solid transparent; + } + + .title { + font-family: nb-theme(font-secondary); + font-size: 1.25rem; + font-weight: nb-theme(font-weight-bold); + color: nb-theme(card-fg-heading); + } + + .status { + font-size: 1rem; + font-weight: nb-theme(font-weight-light); + text-transform: uppercase; + color: nb-theme(card-fg); + } + } + + @include nb-for-theme(cosmic) { + nb-card { + &.off .icon-container { + border-right: 1px solid nb-theme(separator); + } + + .icon-container { + padding: 0; + } + + .details { + padding-left: 1.25rem; + } + + .icon { + width: 7rem; + height: 100%; + font-size: 4.5rem; + border-radius: nb-theme(card-border-radius) 0 0 nb-theme(card-border-radius); + } + + .title { + font-weight: nb-theme(font-weight-bolder); + } + + .status { + font-weight: nb-theme(font-weight-light); + } + } + } +} diff --git a/webapp/src/app/pages/dashboard/status-card/status-card.component.ts b/webapp/src/app/pages/dashboard/status-card/status-card.component.ts new file mode 100644 index 0000000..6260803 --- /dev/null +++ b/webapp/src/app/pages/dashboard/status-card/status-card.component.ts @@ -0,0 +1,26 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'ngx-status-card', + styleUrls: ['./status-card.component.scss'], + template: ` + <nb-card (click)="on = !on" [ngClass]="{'off': !on}"> + <div class="icon-container"> + <div class="icon {{ type }}"> + <ng-content></ng-content> + </div> + </div> + + <div class="details"> + <div class="title">{{ title }}</div> + <div class="status">{{ on ? 'ON' : 'OFF' }}</div> + </div> + </nb-card> + `, +}) +export class StatusCardComponent { + + @Input() title: string; + @Input() type: string; + @Input() on = true; +} diff --git a/webapp/src/app/pages/notifications/notifications.component.scss b/webapp/src/app/pages/notifications/notifications.component.scss new file mode 100644 index 0000000..ce85e8e --- /dev/null +++ b/webapp/src/app/pages/notifications/notifications.component.scss @@ -0,0 +1,28 @@ +@import '../../@theme/styles/themes'; +@import '~bootstrap/scss/mixins/breakpoints'; +@import '~@nebular/theme/styles/global/bootstrap/breakpoints'; + +@include nb-install-component() { + + nb-card-footer { + padding-bottom: 0.25rem; + + button { + margin: 0 1rem 1rem 0; + } + } + + /* stylelint-disable */ + toaster-container /deep/ { + #toast-container .toast-close-button { + right: 0; + } + } + /* stylelint-enable */ + + @include media-breakpoint-down(xs) { + .dropdown-toggle { + font-size: 0.75rem; + } + } +} diff --git a/webapp/src/app/pages/notifications/notifications.component.ts b/webapp/src/app/pages/notifications/notifications.component.ts new file mode 100644 index 0000000..accd150 --- /dev/null +++ b/webapp/src/app/pages/notifications/notifications.component.ts @@ -0,0 +1,65 @@ +import { Component } from '@angular/core'; +import { ToasterService, ToasterConfig, Toast, BodyOutputType } from 'angular2-toaster'; +import { Observable } from 'rxjs/Observable'; +import { AlertService, IAlert } from '../../@core-xds/services/alert.service'; + +import 'style-loader!angular2-toaster/toaster.css'; + +@Component({ + selector: 'ngx-notifications', + styleUrls: ['./notifications.component.scss'], + template: '<toaster-container [toasterconfig]="config"></toaster-container>', +}) +export class NotificationsComponent { + + config: ToasterConfig; + + private position = 'toast-top-full-width'; + private animationType = 'slideDown'; + private toastsLimit = 10; + private toasterService: ToasterService; + private alerts$: Observable<IAlert[]>; + + constructor( + toasterService: ToasterService, + private alertSvr: AlertService, + ) { + this.toasterService = toasterService; + + this.alertSvr.alerts.subscribe(alerts => { + if (alerts.length === 0) { + this.clearToasts(); + } else { + alerts.forEach(al => { + const title = al.type.toUpperCase(); + this.showToast(al.type, title, al.msg, al.dismissTimeout); + }); + } + }); + } + + private showToast(type: string, title: string, body: string, tmo: number) { + this.config = new ToasterConfig({ + positionClass: this.position, + timeout: tmo, + newestOnTop: true, + tapToDismiss: true, // is Hide OnClick + preventDuplicates: false, + animation: this.animationType, + limit: this.toastsLimit, + }); + const toast: Toast = { + type: type, + title: title, + body: body, + timeout: tmo, + showCloseButton: true, + bodyOutputType: BodyOutputType.TrustedHtml, + }; + this.toasterService.popAsync(toast); + } + + clearToasts() { + this.toasterService.clear(); + } +} diff --git a/webapp/src/app/pages/pages-menu.ts b/webapp/src/app/pages/pages-menu.ts new file mode 100644 index 0000000..1a3dd97 --- /dev/null +++ b/webapp/src/app/pages/pages-menu.ts @@ -0,0 +1,201 @@ +import { NbMenuItem } from '@nebular/theme'; + +export const MENU_ITEMS: NbMenuItem[] = [ + { + title: 'XDS-Dashboard', + icon: 'nb-home', + link: '/pages/dashboard', + home: true, + }, + { + title: 'DEVELOPMENT', + group: true, + }, + { + title: 'Projects', + icon: 'nb-keypad', + link: '/pages/projects', + }, + { + title: 'SDKs', + icon: 'fa fa-file-archive-o', + link: '/pages/sdks', + }, + { + title: 'Boards', + icon: 'fa fa-microchip', + children: [ + ], + }, + { + title: 'Build', + icon: 'fa fa-cogs', + link: '/pages/build', + }, + { + title: 'MISC', + group: true, + }, + + { + title: 'Configuration', + icon: 'fa fa-sliders', + link: '/pages/config', + children: [ + { + title: 'Global', + link: '/pages/config/global', + }, + { + title: 'XDS Server', + link: '/pages/config/xds', + }, + ], + }, + /* + { + title: 'UI Features', + icon: 'nb-keypad', + link: '/pages/ui-features', + children: [ + { + title: 'Buttons', + link: '/pages/ui-features/buttons', + }, + { + title: 'Grid', + link: '/pages/ui-features/grid', + }, + { + title: 'Icons', + link: '/pages/ui-features/icons', + }, + { + title: 'Modals', + link: '/pages/ui-features/modals', + }, + { + title: 'Typography', + link: '/pages/ui-features/typography', + }, + { + title: 'Animated Searches', + link: '/pages/ui-features/search-fields', + }, + { + title: 'Tabs', + link: '/pages/ui-features/tabs', + }, + ], + }, + { + title: 'Forms', + icon: 'nb-compose', + children: [ + { + title: 'Form Inputs', + link: '/pages/forms/inputs', + }, + { + title: 'Form Layouts', + link: '/pages/forms/layouts', + }, + ], + }, + { + title: 'Components', + icon: 'nb-gear', + children: [ + { + title: 'Tree', + link: '/pages/components/tree', + }, { + title: 'Notifications', + link: '/pages/components/notifications', + }, + ], + }, + { + title: 'Maps', + icon: 'nb-location', + children: [ + { + title: 'Google Maps', + link: '/pages/maps/gmaps', + }, + { + title: 'Leaflet Maps', + link: '/pages/maps/leaflet', + }, + { + title: 'Bubble Maps', + link: '/pages/maps/bubble', + }, + ], + }, + { + title: 'Charts', + icon: 'nb-bar-chart', + children: [ + { + title: 'Echarts', + link: '/pages/charts/echarts', + }, + { + title: 'Charts.js', + link: '/pages/charts/chartjs', + }, + { + title: 'D3', + link: '/pages/charts/d3', + }, + ], + }, + { + title: 'Editors', + icon: 'nb-title', + children: [ + { + title: 'TinyMCE', + link: '/pages/editors/tinymce', + }, + { + title: 'CKEditor', + link: '/pages/editors/ckeditor', + }, + ], + }, + { + title: 'Tables', + icon: 'nb-tables', + children: [ + { + title: 'Smart Table', + link: '/pages/tables/smart-table', + }, + ], + }, + */ + { + title: 'Auth', + icon: 'nb-locked', + children: [ + { + title: 'Login', + link: '/auth/login', + }, + { + title: 'Register', + link: '/auth/register', + }, + { + title: 'Request Password', + link: '/auth/request-password', + }, + { + title: 'Reset Password', + link: '/auth/reset-password', + }, + ], + }, +]; diff --git a/webapp/src/app/pages/pages-routing.module.ts b/webapp/src/app/pages/pages-routing.module.ts new file mode 100644 index 0000000..11834e8 --- /dev/null +++ b/webapp/src/app/pages/pages-routing.module.ts @@ -0,0 +1,41 @@ +import { RouterModule, Routes } from '@angular/router'; +import { NgModule } from '@angular/core'; + +import { PagesComponent } from './pages.component'; +import { DashboardComponent } from './dashboard/dashboard.component'; +import { ProjectsComponent } from './projects/projects.component'; +import { SdksComponent } from './sdks/sdks.component'; +import { BuildComponent } from './build/build.component'; + +const routes: Routes = [{ + path: '', + component: PagesComponent, + children: [{ + path: 'dashboard', + component: DashboardComponent, + }, { + path: 'projects', + component: ProjectsComponent, + }, { + path: 'sdks', + component: SdksComponent, + }, { + path: 'build', + component: BuildComponent, + }, { + path: 'config', + loadChildren: './config/config.module#ConfigModule', + }, + { + path: '', + redirectTo: 'dashboard', + pathMatch: 'full', + }], +}]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class PagesRoutingModule { +} diff --git a/webapp/src/app/pages/pages.component.ts b/webapp/src/app/pages/pages.component.ts new file mode 100644 index 0000000..a343565 --- /dev/null +++ b/webapp/src/app/pages/pages.component.ts @@ -0,0 +1,18 @@ +import { Component } from '@angular/core'; + +import { MENU_ITEMS } from './pages-menu'; + +@Component({ + selector: 'ngx-pages', + template: ` + <ngx-notifications></ngx-notifications> + <ngx-xds-layout> + <nb-menu [items]="menu"></nb-menu> + <router-outlet></router-outlet> + </ngx-xds-layout> + `, +}) +export class PagesComponent { + + menu = MENU_ITEMS; +} diff --git a/webapp/src/app/pages/pages.module.ts b/webapp/src/app/pages/pages.module.ts new file mode 100644 index 0000000..5e4f3be --- /dev/null +++ b/webapp/src/app/pages/pages.module.ts @@ -0,0 +1,33 @@ +import { NgModule } from '@angular/core'; +import { ToasterModule } from 'angular2-toaster'; + +import { PagesComponent } from './pages.component'; +import { DashboardModule } from './dashboard/dashboard.module'; +import { BuildModule } from './build/build.module'; +import { ProjectsModule } from './projects/projects.module'; +import { SdksModule } from './sdks/sdks.module'; +import { PagesRoutingModule } from './pages-routing.module'; +import { NotificationsComponent } from './notifications/notifications.component'; +import { ThemeModule } from '../@theme/theme.module'; + +const PAGES_COMPONENTS = [ + PagesComponent, + NotificationsComponent, +]; + +@NgModule({ + imports: [ + PagesRoutingModule, + ThemeModule, + BuildModule, + DashboardModule, + ProjectsModule, + SdksModule, + ToasterModule, + ], + declarations: [ + ...PAGES_COMPONENTS, + ], +}) +export class PagesModule { +} diff --git a/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.html b/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.html new file mode 100644 index 0000000..e2c6748 --- /dev/null +++ b/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.html @@ -0,0 +1,51 @@ +<div class="modal-header"> + <span>Add a new project</span> + <button class="close" aria-label="Close" (click)="closeModal()"> + <span aria-hidden="true">×</span> + </button> +</div> + +<div class="modal-body row"> + <div class="col-12"> + <form [formGroup]="addProjectForm" (ngSubmit)="onSubmit()"> + + <div class="form-group row"> + <label for="sharing-type" class="col-sm-3 col-form-label">Sharing Type</label> + <div class="col-sm-9"> + <select id="select-sharing-type" class="form-control" formControlName="type"> + <option *ngFor="let t of projectTypes" [value]="t.value">{{t.display}} + </option> + </select> + </div> + </div> + + <div class="form-group row"> + <label for="select-local-path" class="col-sm-3 col-form-label">Local Path</label> + <div class="col-sm-9"> + <input type="text" id="inputLocalPath" class="form-control" formControlName="pathCli" placeholder="/tmp/myProject" (change)="onChangeLocalProject($event)"> + </div> + </div> + + <div class="form-group row"> + <label for="select-server-path" class="col-sm-3 col-form-label">Server Path</label> + <div class="col-sm-9"> + <input type="text" id="inputServerPath" class="form-control" formControlName="pathSvr"> + </div> + </div> + + <div class="form-group row"> + <label for="select-label" class="col-sm-3 col-form-label">Label</label> + <div class="col-sm-9"> + <input type="text" id="inputLabel" class="form-control" formControlName="label" (keyup)="onKeyLabel($event)"> + </div> + </div> + + <div class="offset-sm-3 col-sm-9"> + <button class="btn btn-md btn-secondary" (click)="cancelAction=true; closeModal()"> Cancel </button> + <button class="btn btn-md btn-primary" type="submit" [disabled]="!addProjectForm.valid">Add Folder</button> + </div> + </form> + </div> +</div> +<div class="modal-footer form-group"> +</div> diff --git a/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.scss b/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.scss new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.scss @@ -0,0 +1 @@ + diff --git a/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.ts b/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.ts new file mode 100644 index 0000000..640ac5c --- /dev/null +++ b/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.ts @@ -0,0 +1,143 @@ +import { Component, Input, ViewChild, OnInit } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { FormControl, FormGroup, Validators, ValidationErrors, FormBuilder, ValidatorFn, AbstractControl } from '@angular/forms'; + +// Import RxJs required methods +import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/filter'; +import 'rxjs/add/operator/debounceTime'; + +import { AlertService, IAlert } from '../../../@core-xds/services/alert.service'; +import { ProjectService, IProject, ProjectType, ProjectTypes } from '../../../@core-xds/services/project.service'; +import { XDSConfigService } from '../../../@core-xds/services/xds-config.service'; + + +@Component({ + selector: 'xds-project-add-modal', + templateUrl: 'project-add-modal.component.html', + styleUrls: ['project-add-modal.component.scss'] +}) +export class ProjectAddModalComponent implements OnInit { + // @Input('server-id') serverID: string; + private serverID: string; + + cancelAction = false; + userEditedLabel = false; + projectTypes = ProjectTypes; + + addProjectForm: FormGroup; + typeCtrl: FormControl; + pathCliCtrl: FormControl; + pathSvrCtrl: FormControl; + + constructor( + private alert: AlertService, + private projectSvr: ProjectService, + private XdsConfigSvr: XDSConfigService, + private fb: FormBuilder, + private activeModal: NgbActiveModal + ) { + // Define types (first one is special/placeholder) + this.projectTypes.unshift({ value: ProjectType.UNSET, display: '--Select a type--' }); + + this.typeCtrl = new FormControl(this.projectTypes[0].value, this.validatorProjType); + this.pathCliCtrl = new FormControl('', this.validatorProjPath); + this.pathSvrCtrl = new FormControl({ value: '', disabled: true }, this.validatorProjPath); + + this.addProjectForm = fb.group({ + type: this.typeCtrl, + pathCli: this.pathCliCtrl, + pathSvr: this.pathSvrCtrl, + label: ['', Validators.nullValidator], + }); + } + + + ngOnInit() { + // Update server ID + this.serverID = this.XdsConfigSvr.getCurServer().id; + this.XdsConfigSvr.onCurServer().subscribe(svr => this.serverID = svr.id); + + // Auto create label name + this.pathCliCtrl.valueChanges + .debounceTime(100) + .filter(n => n) + .map(n => { + const last = n.split('/'); + let nm = n; + if (last.length > 0) { + nm = last.pop(); + if (nm === '' && last.length > 0) { + nm = last.pop(); + } + } + return 'Project_' + nm; + }) + .subscribe(value => { + if (value && !this.userEditedLabel) { + this.addProjectForm.patchValue({ label: value }); + } + }); + + // Handle disabling of Server path + this.typeCtrl.valueChanges + .debounceTime(500) + .subscribe(valType => { + const dis = (valType === String(ProjectType.SYNCTHING)); + this.pathSvrCtrl.reset({ value: '', disabled: dis }); + }); + } + + closeModal() { + this.activeModal.close(); + } + + onKeyLabel(event: any) { + this.userEditedLabel = (this.addProjectForm.value.label !== ''); + } + + onChangeLocalProject(e) { + } + + onSubmit() { + if (this.cancelAction) { + return; + } + + const formVal = this.addProjectForm.value; + + const type = formVal['type'].value; + this.projectSvr.Add({ + serverId: this.serverID, + label: formVal['label'], + pathClient: formVal['pathCli'], + pathServer: formVal['pathSvr'], + type: formVal['type'], + // FIXME: allow to set defaultSdkID from New Project config panel + }) + .subscribe(prj => { + this.alert.info('Project ' + prj.label + ' successfully created.'); + this.closeModal(); + + // Reset Value for the next creation + this.addProjectForm.reset(); + const selectedType = this.projectTypes[0].value; + this.addProjectForm.patchValue({ type: selectedType }); + + }, + err => { + this.alert.error(err, 60); + this.closeModal(); + }); + } + + private validatorProjType(g: FormGroup): ValidationErrors | null { + return (g.value !== ProjectType.UNSET) ? null : { validatorProjType: { valid: false } }; + } + + private validatorProjPath(g: FormGroup): ValidationErrors | null { + return (g.disabled || g.value !== '') ? null : { validatorProjPath: { valid: false } }; + } + +} diff --git a/webapp/src/app/pages/projects/project-card/project-card.component.html b/webapp/src/app/pages/projects/project-card/project-card.component.html new file mode 100644 index 0000000..c54d581 --- /dev/null +++ b/webapp/src/app/pages/projects/project-card/project-card.component.html @@ -0,0 +1,65 @@ +<nb-card class="xds-projects"> + <nb-card-header> + + <div class="row"> + <div class="col-12 col-md-8"> + {{ project.label }} + </div> + <div class="col-6 col-md-4 text-right" role="group"> + <button class="btn btn-outline-danger btn-tn btn-xds" (click)="delete(project)"> + <span class="fa fa-trash fa-size-x2"></span> + </button> + </div> + </div> + </nb-card-header> + + <nb-card-body> + <table class="table table-striped"> + <tbody> + <tr> + <th> + <span class="fa fa-fw fa-id-badge"></span> + <span>Project ID</span> + </th> + <td>{{ project.id }}</td> + </tr> + <tr> + <th> + <span class="fa fa-fw fa-exchange"></span> + <span>Sharing type</span> + </th> + <td>{{ project.type | readableType }}</td> + </tr> + <tr> + <th> + <span class="fa fa-fw fa-folder-open-o"></span> + <span>Local path</span> + </th> + <td>{{ project.pathClient }}</td> + </tr> + <tr *ngIf="project.pathServer && project.pathServer != ''"> + <th> + <span class="fa fa-fw fa-folder-open-o"></span> + <span>Server path</span> + </th> + <td>{{ project.pathServer }}</td> + </tr> + <tr> + <th> + <span class="fa fa-fw fa-flag"></span> + <span>Status</span> + </th> + <td>{{ project.status }} - {{ project.isInSync ? "Up to Date" : "Out of Sync"}} + <button *ngIf="!project.isInSync" class="btn btn-outline-info btn-tn btn-xds" (click)="sync(project)" style="margin-left:2em;"> + <span class="fa fa-refresh fa-size-x2"></span> + </button> + </td> + </tr> + </tbody> + </table> + </nb-card-body> + + <nb-card-footer> + <!-- <pre>{{project | json}}</pre> --> + </nb-card-footer> +</nb-card> diff --git a/webapp/src/app/pages/projects/project-card/project-card.component.scss b/webapp/src/app/pages/projects/project-card/project-card.component.scss new file mode 100644 index 0000000..a433f58 --- /dev/null +++ b/webapp/src/app/pages/projects/project-card/project-card.component.scss @@ -0,0 +1,54 @@ +@import '~@nebular/theme/styles/global/bootstrap/buttons'; + +.xds-project-card .icon { + padding: 0.75rem 0; + font-size: 1.75rem; +} + +nb-card-body { + padding: 0; +} + +nb-card-footer { + text-align: right; +} + +.fa-big { + font-size: 20px; + font-weight: bold; +} + +.fa-size-x2 { + font-size: 20px; +} + +th span { + font-weight: 100; +} + +th label { + font-weight: 100; + margin-bottom: 0; +} + +tr.info>th { + vertical-align: middle; +} + +tr.info>td { + vertical-align: middle; +} + +.btn-outline-danger.btn-xds { + color: #ff4c6a; + &:focus { + color: white; + } +} + +.btn-outline-info.btn-xds { + color: #4ca6ff; + &:focus { + color: white; + } +} diff --git a/webapp/src/app/pages/projects/project-card/project-card.component.ts b/webapp/src/app/pages/projects/project-card/project-card.component.ts new file mode 100644 index 0000000..160c4c8 --- /dev/null +++ b/webapp/src/app/pages/projects/project-card/project-card.component.ts @@ -0,0 +1,52 @@ +import { Component, Input, Pipe, PipeTransform } from '@angular/core'; +import { ProjectService, IProject, ProjectType, ProjectTypeEnum } from '../../../@core-xds/services/project.service'; +import { AlertService } from '../../../@core-xds/services/alert.service'; + + +@Component({ + selector: 'xds-project-card', + styleUrls: ['./project-card.component.scss'], + templateUrl: './project-card.component.html', +}) +export class ProjectCardComponent { + + // FIXME workaround of https://github.com/angular/angular-cli/issues/2034 + // should be removed with angular 5 + // @Input() project: IProject; + @Input() project = <IProject>null; + + constructor( + private alert: AlertService, + private projectSvr: ProjectService + ) { + } + + delete(prj: IProject) { + this.projectSvr.Delete(prj).subscribe( + res => { }, + err => this.alert.error('ERROR delete: ' + err) + ); + } + + sync(prj: IProject) { + this.projectSvr.Sync(prj).subscribe( + res => { }, + err => this.alert.error('ERROR: ' + err) + ); + } +} + +// Make Project type human readable +@Pipe({ + name: 'readableType' +}) + +export class ProjectReadableTypePipe implements PipeTransform { + transform(type: ProjectTypeEnum): string { + switch (type) { + case ProjectType.NATIVE_PATHMAP: return 'Native (path mapping)'; + case ProjectType.SYNCTHING: return 'Cloud (Syncthing)'; + default: return String(type); + } + } +} diff --git a/webapp/src/app/pages/projects/projects.component.html b/webapp/src/app/pages/projects/projects.component.html new file mode 100644 index 0000000..662dfcc --- /dev/null +++ b/webapp/src/app/pages/projects/projects.component.html @@ -0,0 +1,26 @@ +<div class="row"> + <div class="col-12"> + <nb-card-body> + <div class="col-9"> + <nb-actions size="medium"> + <nb-action> + <button (click)="add()"> + <i class="nb-plus"></i> + <span>Add Project</span> + </button> + </nb-action> + </nb-actions> + </div> + <div class="col-3 right"> + <nb-actions size="medium"> + <nb-action> + <nb-search type="rotate-layout"></nb-search> + </nb-action> + </nb-actions> + </div> + </nb-card-body> + </div> + <div class="col-md-12 col-lg-12 col-xxxl-6" *ngFor="let prj of (projects$ | async)"> + <xds-project-card [project]="prj"></xds-project-card> + </div> +</div> diff --git a/webapp/src/app/pages/projects/projects.component.scss b/webapp/src/app/pages/projects/projects.component.scss new file mode 100644 index 0000000..3631fbb --- /dev/null +++ b/webapp/src/app/pages/projects/projects.component.scss @@ -0,0 +1,83 @@ +@import '../../@theme/styles/themes'; +@import '~@nebular/theme/components/card/card.component.theme'; +@import '~bootstrap/scss/mixins/breakpoints'; +@import '~@nebular/theme/styles/global/bootstrap/breakpoints'; +@include nb-install-component() { + nb-card-body { + display: flex; + align-items: center; + } + .action-groups-header { + flex-basis: 20%; + color: nb-theme(card-header-fg-heading); + font-family: nb-theme(card-header-font-family); + font-size: nb-theme(card-header-font-size); + font-weight: nb-theme(card-header-font-weight); + } + .nb-actions { + flex-basis: 80%; + } + .right { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + order: 1; + flex-direction: row-reverse; + } + nb-actions>nb-action { + padding: 0; + } + nb-action { + i { + color: nb-theme(color-fg); + font-size: 2.5rem; + margin-right: 1rem; + } + span { + font-family: nb-theme(font-secondary); + font-weight: nb-theme(font-weight-bold); + color: nb-theme(color-fg-heading); + text-transform: uppercase; + } + button { + margin: 0 auto; + padding: 0; + cursor: pointer; + border: none; + background: none; + display: flex; + align-items: center; + &:focus { + box-shadow: none; + outline: none; + } + } + } + @include media-breakpoint-down(md) { + nb-actions nb-action { + padding: 0 0.75rem; + } + } + @include media-breakpoint-down(sm) { + nb-card-body { + padding: 1rem; + } + nb-action { + font-size: 0.75rem; + i { + font-size: 2rem; + margin-right: 0.5rem; + } + } + } + @include media-breakpoint-down(is) { + nb-action i { + font-size: 1.75rem; + margin: 0; + } + span { + display: none; + } + } +} diff --git a/webapp/src/app/pages/projects/projects.component.ts b/webapp/src/app/pages/projects/projects.component.ts new file mode 100644 index 0000000..179bbd0 --- /dev/null +++ b/webapp/src/app/pages/projects/projects.component.ts @@ -0,0 +1,33 @@ +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; + +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { ProjectAddModalComponent } from './project-add-modal/project-add-modal.component'; + +import { ProjectService, IProject } from '../../@core-xds/services/project.service'; + +@Component({ + selector: 'xds-projects', + styleUrls: ['./projects.component.scss'], + templateUrl: './projects.component.html', +}) +export class ProjectsComponent implements OnInit { + + projects$: Observable<IProject[]>; + projects: IProject[]; + + constructor( + private projectSvr: ProjectService, + private modalService: NgbModal, + ) { + } + + ngOnInit() { + this.projects$ = this.projectSvr.Projects$; + } + + add() { + const activeModal = this.modalService.open(ProjectAddModalComponent, { size: 'lg', container: 'nb-layout' }); + activeModal.componentInstance.modalHeader = 'Large Modal'; + } +} diff --git a/webapp/src/app/pages/projects/projects.module.ts b/webapp/src/app/pages/projects/projects.module.ts new file mode 100644 index 0000000..48f37ce --- /dev/null +++ b/webapp/src/app/pages/projects/projects.module.ts @@ -0,0 +1,23 @@ +import { NgModule } from '@angular/core'; +import { ThemeModule } from '../../@theme/theme.module'; + +import { ProjectsComponent } from './projects.component'; +import { ProjectCardComponent, ProjectReadableTypePipe } from './project-card/project-card.component'; +import { ProjectAddModalComponent } from './project-add-modal/project-add-modal.component'; + + +@NgModule({ + imports: [ + ThemeModule, + ], + declarations: [ + ProjectsComponent, + ProjectCardComponent, + ProjectAddModalComponent, + ProjectReadableTypePipe, + ], + entryComponents: [ + ProjectAddModalComponent + ], +}) +export class ProjectsModule { } diff --git a/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.html b/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.html new file mode 100644 index 0000000..0c2787c --- /dev/null +++ b/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.html @@ -0,0 +1,48 @@ +<nb-card class="xds-sdks"> + <nb-card-header> + + <div class="row"> + <div class="col-12 col-md-8"> + {{ labelGet(sdk) }} + </div> + <div class="col-6 col-md-4 text-right" role="group"> + <button class="btn btn-outline-danger btn-tn btn-xds" (click)="delete(sdk)"> + <span class="fa fa-trash fa-size-x2"></span> + </button> + </div> + </div> + </nb-card-header> + + <nb-card-body> + <table class="table table-striped"> + <tbody> + <tr> + <th> + <span class="fa fa-fw fa-id-badge"></span> + <span>SDK ID</span> + </th> + <td>{{ sdk.id }}</td> + </tr> + <tr> + <th><span class="fa fa-fw fa-user"></span> <span>Profile</span></th> + <td>{{ sdk.profile }}</td> + </tr> <tr> + <th><span class="fa fa-fw fa-tasks"></span> <span>Architecture</span></th> + <td>{{ sdk.arch }}</td> + </tr> + <tr> + <th><span class="fa fa-fw fa-code-fork"></span> <span>Version</span></th> + <td>{{ sdk.version }}</td> + </tr> + <tr> + <th><span class="fa fa-fw fa-folder-open-o"></span> <span>Sdk path</span></th> + <td>{{ sdk.path}}</td> + </tr> + </tbody> + </table> + </nb-card-body> + + <nb-card-footer> + <!-- <pre>{{sdk | json}}</pre> --> + </nb-card-footer> +</nb-card> diff --git a/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.scss b/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.scss new file mode 100644 index 0000000..e404610 --- /dev/null +++ b/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.scss @@ -0,0 +1,47 @@ +@import '~@nebular/theme/styles/global/bootstrap/buttons'; + +.xds-sdk-card .icon { + padding: 0.75rem 0; + font-size: 1.75rem; +} + +nb-card-body { + padding: 0; +} + +nb-card-footer { + text-align: right; +} + +.fa-big { + font-size: 20px; + font-weight: bold; +} + +.fa-size-x2 { + font-size: 20px; +} + +th span { + font-weight: 100; +} + +th label { + font-weight: 100; + margin-bottom: 0; +} + +tr.info>th { + vertical-align: middle; +} + +tr.info>td { + vertical-align: middle; +} + +.btn-outline-danger.btn-xds { + color: #ff4c6a; + &:focus { + color: white; + } +} diff --git a/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.ts b/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.ts new file mode 100644 index 0000000..8228570 --- /dev/null +++ b/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.ts @@ -0,0 +1,35 @@ +import { Component, Input, Pipe, PipeTransform } from '@angular/core'; +import { SdkService, ISdk } from '../../../@core-xds/services/sdk.service'; +import { AlertService } from '../../../@core-xds/services/alert.service'; + + +@Component({ + selector: 'xds-sdk-card', + styleUrls: ['./sdk-card.component.scss'], + templateUrl: './sdk-card.component.html', +}) +export class SdkCardComponent { + + // FIXME workaround of https://github.com/angular/angular-cli/issues/2034 + // should be removed with angular 5 + // @Input() sdk: ISdk; + @Input() sdk = <ISdk>null; + + constructor( + private alert: AlertService, + private sdkSvr: SdkService + ) { + } + + labelGet(sdk: ISdk) { + return sdk.profile + '-' + sdk.arch + '-' + sdk.version; + } + + delete(sdk: ISdk) { + this.sdkSvr.delete(sdk).subscribe( + res => { }, + err => this.alert.error('ERROR delete: ' + err) + ); + } +} + diff --git a/webapp/src/app/pages/sdks/sdks.component.html b/webapp/src/app/pages/sdks/sdks.component.html new file mode 100644 index 0000000..adfd924 --- /dev/null +++ b/webapp/src/app/pages/sdks/sdks.component.html @@ -0,0 +1,26 @@ +<div class="row"> + <div class="col-12"> + <nb-card-body> + <div class="col-9"> + <nb-actions size="medium"> + <nb-action> + <button (click)="add()"> + <i class="nb-plus"></i> + <span>Add new SDK</span> + </button> + </nb-action> + </nb-actions> + </div> + <div class="col-3 right"> + <nb-actions size="medium"> + <nb-action> + <nb-search type="rotate-layout"></nb-search> + </nb-action> + </nb-actions> + </div> + </nb-card-body> + </div> + <div class="col-md-12 col-lg-12 col-xxxl-6" *ngFor="let sdk of (sdks$ | async)"> + <xds-sdk-card [sdk]="sdk"></xds-sdk-card> + </div> +</div> diff --git a/webapp/src/app/pages/sdks/sdks.component.scss b/webapp/src/app/pages/sdks/sdks.component.scss new file mode 100644 index 0000000..3631fbb --- /dev/null +++ b/webapp/src/app/pages/sdks/sdks.component.scss @@ -0,0 +1,83 @@ +@import '../../@theme/styles/themes'; +@import '~@nebular/theme/components/card/card.component.theme'; +@import '~bootstrap/scss/mixins/breakpoints'; +@import '~@nebular/theme/styles/global/bootstrap/breakpoints'; +@include nb-install-component() { + nb-card-body { + display: flex; + align-items: center; + } + .action-groups-header { + flex-basis: 20%; + color: nb-theme(card-header-fg-heading); + font-family: nb-theme(card-header-font-family); + font-size: nb-theme(card-header-font-size); + font-weight: nb-theme(card-header-font-weight); + } + .nb-actions { + flex-basis: 80%; + } + .right { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + order: 1; + flex-direction: row-reverse; + } + nb-actions>nb-action { + padding: 0; + } + nb-action { + i { + color: nb-theme(color-fg); + font-size: 2.5rem; + margin-right: 1rem; + } + span { + font-family: nb-theme(font-secondary); + font-weight: nb-theme(font-weight-bold); + color: nb-theme(color-fg-heading); + text-transform: uppercase; + } + button { + margin: 0 auto; + padding: 0; + cursor: pointer; + border: none; + background: none; + display: flex; + align-items: center; + &:focus { + box-shadow: none; + outline: none; + } + } + } + @include media-breakpoint-down(md) { + nb-actions nb-action { + padding: 0 0.75rem; + } + } + @include media-breakpoint-down(sm) { + nb-card-body { + padding: 1rem; + } + nb-action { + font-size: 0.75rem; + i { + font-size: 2rem; + margin-right: 0.5rem; + } + } + } + @include media-breakpoint-down(is) { + nb-action i { + font-size: 1.75rem; + margin: 0; + } + span { + display: none; + } + } +} diff --git a/webapp/src/app/pages/sdks/sdks.component.ts b/webapp/src/app/pages/sdks/sdks.component.ts new file mode 100644 index 0000000..3208121 --- /dev/null +++ b/webapp/src/app/pages/sdks/sdks.component.ts @@ -0,0 +1,35 @@ +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; + +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +//import { SdkAddModalComponent } from './sdk-add-modal/sdk-add-modal.component'; + +import { SdkService, ISdk } from '../../@core-xds/services/sdk.service'; + +@Component({ + selector: 'xds-sdks', + styleUrls: ['./sdks.component.scss'], + templateUrl: './sdks.component.html', +}) +export class SdksComponent implements OnInit { + + sdks$: Observable<ISdk[]>; + sdks: ISdk[]; + + constructor( + private sdkSvr: SdkService, + private modalService: NgbModal, + ) { + } + + ngOnInit() { + this.sdks$ = this.sdkSvr.Sdks$; + } + + add() { + /* SEB TODO + const activeModal = this.modalService.open(SdkAddModalComponent, { size: 'lg', container: 'nb-layout' }); + activeModal.componentInstance.modalHeader = 'Large Modal'; + */ + } +} diff --git a/webapp/src/app/pages/sdks/sdks.module.ts b/webapp/src/app/pages/sdks/sdks.module.ts new file mode 100644 index 0000000..48629f6 --- /dev/null +++ b/webapp/src/app/pages/sdks/sdks.module.ts @@ -0,0 +1,22 @@ +import { NgModule } from '@angular/core'; +import { ThemeModule } from '../../@theme/theme.module'; + +import { SdksComponent } from './sdks.component'; +import { SdkCardComponent } from './sdk-card/sdk-card.component'; +//import { SdkAddModalComponent } from './sdk-add-modal/sdk-add-modal.component'; + + +@NgModule({ + imports: [ + ThemeModule, + ], + declarations: [ + SdksComponent, + SdkCardComponent, + //SdkAddModalComponent, + ], + entryComponents: [ + //SdkAddModalComponent + ], +}) +export class SdksModule { } |