summaryrefslogtreecommitdiffstats
path: root/webapp/src/app/pages
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/src/app/pages')
-rw-r--r--webapp/src/app/pages/build/build.component.html63
-rw-r--r--webapp/src/app/pages/build/build.component.scss37
-rw-r--r--webapp/src/app/pages/build/build.component.spec.ts25
-rw-r--r--webapp/src/app/pages/build/build.component.ts198
-rw-r--r--webapp/src/app/pages/build/build.module.ts20
-rw-r--r--webapp/src/app/pages/build/settings/project-select-dropdown.component.ts40
-rw-r--r--webapp/src/app/pages/build/settings/sdk-select-dropdown.component.ts44
-rw-r--r--webapp/src/app/pages/config/config-global/config-global.component.html37
-rw-r--r--webapp/src/app/pages/config/config-global/config-global.component.scss20
-rw-r--r--webapp/src/app/pages/config/config-global/config-global.component.ts25
-rw-r--r--webapp/src/app/pages/config/config-routing.module.ts32
-rw-r--r--webapp/src/app/pages/config/config-xds/config-xds.component.html34
-rw-r--r--webapp/src/app/pages/config/config-xds/config-xds.component.scss26
-rw-r--r--webapp/src/app/pages/config/config-xds/config-xds.component.ts63
-rw-r--r--webapp/src/app/pages/config/config-xds/downloadXdsAgent.component.ts35
-rw-r--r--webapp/src/app/pages/config/config.component.ts10
-rw-r--r--webapp/src/app/pages/config/config.module.ts15
-rw-r--r--webapp/src/app/pages/dashboard/dashboard.component.html10
-rw-r--r--webapp/src/app/pages/dashboard/dashboard.component.scss16
-rw-r--r--webapp/src/app/pages/dashboard/dashboard.component.ts9
-rw-r--r--webapp/src/app/pages/dashboard/dashboard.module.ts52
-rw-r--r--webapp/src/app/pages/dashboard/status-card/status-card.component.scss142
-rw-r--r--webapp/src/app/pages/dashboard/status-card/status-card.component.ts26
-rw-r--r--webapp/src/app/pages/notifications/notifications.component.scss28
-rw-r--r--webapp/src/app/pages/notifications/notifications.component.ts65
-rw-r--r--webapp/src/app/pages/pages-menu.ts201
-rw-r--r--webapp/src/app/pages/pages-routing.module.ts41
-rw-r--r--webapp/src/app/pages/pages.component.ts18
-rw-r--r--webapp/src/app/pages/pages.module.ts33
-rw-r--r--webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.html51
-rw-r--r--webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.scss1
-rw-r--r--webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.ts143
-rw-r--r--webapp/src/app/pages/projects/project-card/project-card.component.html65
-rw-r--r--webapp/src/app/pages/projects/project-card/project-card.component.scss54
-rw-r--r--webapp/src/app/pages/projects/project-card/project-card.component.ts52
-rw-r--r--webapp/src/app/pages/projects/projects.component.html26
-rw-r--r--webapp/src/app/pages/projects/projects.component.scss83
-rw-r--r--webapp/src/app/pages/projects/projects.component.ts33
-rw-r--r--webapp/src/app/pages/projects/projects.module.ts23
-rw-r--r--webapp/src/app/pages/sdks/sdk-card/sdk-card.component.html48
-rw-r--r--webapp/src/app/pages/sdks/sdk-card/sdk-card.component.scss47
-rw-r--r--webapp/src/app/pages/sdks/sdk-card/sdk-card.component.ts35
-rw-r--r--webapp/src/app/pages/sdks/sdks.component.html26
-rw-r--r--webapp/src/app/pages/sdks/sdks.component.scss83
-rw-r--r--webapp/src/app/pages/sdks/sdks.component.ts35
-rw-r--r--webapp/src/app/pages/sdks/sdks.module.ts22
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">&times;</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>&nbsp;
+ <span>Project ID</span>
+ </th>
+ <td>{{ project.id }}</td>
+ </tr>
+ <tr>
+ <th>
+ <span class="fa fa-fw fa-exchange"></span>&nbsp;
+ <span>Sharing type</span>
+ </th>
+ <td>{{ project.type | readableType }}</td>
+ </tr>
+ <tr>
+ <th>
+ <span class="fa fa-fw fa-folder-open-o"></span>&nbsp;
+ <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>&nbsp;
+ <span>Server path</span>
+ </th>
+ <td>{{ project.pathServer }}</td>
+ </tr>
+ <tr>
+ <th>
+ <span class="fa fa-fw fa-flag"></span>&nbsp;
+ <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>&nbsp;
+ <span>SDK ID</span>
+ </th>
+ <td>{{ sdk.id }}</td>
+ </tr>
+ <tr>
+ <th><span class="fa fa-fw fa-user"></span>&nbsp;<span>Profile</span></th>
+ <td>{{ sdk.profile }}</td>
+ </tr> <tr>
+ <th><span class="fa fa-fw fa-tasks"></span>&nbsp;<span>Architecture</span></th>
+ <td>{{ sdk.arch }}</td>
+ </tr>
+ <tr>
+ <th><span class="fa fa-fw fa-code-fork"></span>&nbsp;<span>Version</span></th>
+ <td>{{ sdk.version }}</td>
+ </tr>
+ <tr>
+ <th><span class="fa fa-fw fa-folder-open-o"></span>&nbsp;<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 { }