diff options
author | Sebastien Douheret <sebastien.douheret@iot.bzh> | 2017-08-26 11:29:56 +0200 |
---|---|---|
committer | Sebastien Douheret <sebastien.douheret@iot.bzh> | 2017-08-26 11:29:56 +0200 |
commit | 5e120c466686880c5bf6b94043dd01edc261fef9 (patch) | |
tree | 9d3071f226bd0b234540bd20bdab0c77d3bca164 /webapp | |
parent | e113bbc75f88457d29f11823af0ff902e7c2ac8b (diff) | |
parent | 0367a6f9f2f868d785f197a052840ec621a681a6 (diff) |
Merge remote-tracking branch 'origin/wip'
Diffstat (limited to 'webapp')
27 files changed, 716 insertions, 273 deletions
diff --git a/webapp/assets/images/iot-bzh-logo-small.png b/webapp/assets/images/iot-bzh-logo-small.png Binary files differnew file mode 100644 index 0000000..2c3b2ae --- /dev/null +++ b/webapp/assets/images/iot-bzh-logo-small.png diff --git a/webapp/src/app/app.component.css b/webapp/src/app/app.component.css index 0ec4936..a47ad13 100644 --- a/webapp/src/app/app.component.css +++ b/webapp/src/app/app.component.css @@ -1,13 +1,18 @@ -.navbar-inverse { - background-color: #330066; +.navbar { + background-color: whitesmoke; } .navbar-brand { - background: #330066; - color: white; font-size: x-large; + font-variant: small-caps; + color: #5a28a1; } +a.navbar-brand { + margin-top: 5px; +} + + .navbar-nav ul li a { color: #fff; } @@ -15,3 +20,12 @@ .menu-text { color: #fff; } + +#logo-iot { + padding: 0 2px; + height: 60px; +} + +li>a { + color:#5a28a1; +} diff --git a/webapp/src/app/app.component.html b/webapp/src/app/app.component.html index b02dbe2..a889b12 100644 --- a/webapp/src/app/app.component.html +++ b/webapp/src/app/app.component.html @@ -1,4 +1,5 @@ -<nav class="navbar navbar-fixed-top navbar-inverse"> +<nav class="navbar navbar-fixed-top"> + <!-- navbar-inverse"> --> <div class="container-fluid"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#myNavbar" @@ -7,6 +8,8 @@ <span class="icon-bar"></span> <span class="icon-bar"></span> </button> + + <img class="navbar-brand" id="logo-iot" src="assets/images/iot-bzh-logo-small.png"> <a class="navbar-brand" href="#">X(cross) Development System Dashboard</a> </div> @@ -24,4 +27,4 @@ <div style="margin:10px;"> <router-outlet></router-outlet> -</div>
\ No newline at end of file +</div> diff --git a/webapp/src/app/app.module.ts b/webapp/src/app/app.module.ts index 4877f6e..10ff7a4 100644 --- a/webapp/src/app/app.module.ts +++ b/webapp/src/app/app.module.ts @@ -10,6 +10,7 @@ import { ModalModule } from 'ngx-bootstrap/modal'; import { AccordionModule } from 'ngx-bootstrap/accordion'; import { CarouselModule } from 'ngx-bootstrap/carousel'; import { PopoverModule } from 'ngx-bootstrap/popover'; +import { CollapseModule } from 'ngx-bootstrap/collapse'; import { BsDropdownModule } from 'ngx-bootstrap/dropdown'; // Import the application components and services. @@ -21,9 +22,11 @@ import { DlXdsAgentComponent, CapitalizePipe } from "./config/downloadXdsAgent.c import { ProjectCardComponent } from "./projects/projectCard.component"; import { ProjectReadableTypePipe } from "./projects/projectCard.component"; import { ProjectsListAccordionComponent } from "./projects/projectsListAccordion.component"; +import { ProjectAddModalComponent} from "./projects/projectAddModal.component"; import { SdkCardComponent } from "./sdks/sdkCard.component"; import { SdksListAccordionComponent } from "./sdks/sdksListAccordion.component"; import { SdkSelectDropdownComponent } from "./sdks/sdkSelectDropdown.component"; +import { SdkAddModalComponent} from "./sdks/sdkAddModal.component"; import { HomeComponent } from "./home/home.component"; import { DevelComponent } from "./devel/devel.component"; @@ -52,6 +55,7 @@ import { SdkService } from "./services/sdk.service"; AccordionModule.forRoot(), CarouselModule.forRoot(), PopoverModule.forRoot(), + CollapseModule.forRoot(), BsDropdownModule.forRoot(), ], declarations: [ @@ -67,9 +71,11 @@ import { SdkService } from "./services/sdk.service"; ProjectCardComponent, ProjectReadableTypePipe, ProjectsListAccordionComponent, + ProjectAddModalComponent, SdkCardComponent, SdksListAccordionComponent, SdkSelectDropdownComponent, + SdkAddModalComponent, ], providers: [ AppRoutingProviders, @@ -88,4 +94,4 @@ import { SdkService } from "./services/sdk.service"; bootstrap: [AppComponent] }) export class AppModule { -}
\ No newline at end of file +} diff --git a/webapp/src/app/config/config.component.css b/webapp/src/app/config/config.component.css index f480857..6412f9a 100644 --- a/webapp/src/app/config/config.component.css +++ b/webapp/src/app/config/config.component.css @@ -1,3 +1,8 @@ +.fa-big { + font-size: 20px; + font-weight: bold; +} + .fa-size-x2 { font-size: 20px; } @@ -23,4 +28,8 @@ tr.info>th { tr.info>td { vertical-align: middle; -}
\ No newline at end of file +} + +.panel-heading { + background: aliceblue; +} diff --git a/webapp/src/app/config/config.component.html b/webapp/src/app/config/config.component.html index d9229d5..c36ba02 100644 --- a/webapp/src/app/config/config.component.html +++ b/webapp/src/app/config/config.component.html @@ -1,11 +1,17 @@ <div class="panel panel-default"> - <div class="panel-heading clearfix"> - <h2 class="panel-title pull-left">Global Configuration</h2> - <div class="pull-right"> - <span class="fa fa-fw fa-exchange fa-size-x2" [style.color]="((serverStatus$ | async)?.WS_connected)?'green':'red'"></span> - </div> + <div class="panel-heading"> + <h2 class="panel-title" (click)="gConfigIsCollapsed = !gConfigIsCollapsed"> + Global Configuration + <div class="pull-right"> + <span class="fa fa-fw fa-exchange fa-size-x2" [style.color]="((serverStatus$ | async)?.WS_connected)?'green':'red'"></span> + + <button class="btn btn-link" (click)="gConfigIsCollapsed = !gConfigIsCollapsed; $event.stopPropagation()"> + <span class="fa fa-big" [ngClass]="{'fa-angle-double-down': gConfigIsCollapsed, 'fa-angle-double-right': !gConfigIsCollapsed}"></span> + </button> + </div> + </h2> </div> - <div class="panel-body"> + <div class="panel-body" [collapse]="gConfigIsCollapsed && (agentStatus$ | async)?.connected"> <div class="row"> <div class="col-xs-12"> <table class="table table-condensed"> @@ -50,9 +56,18 @@ <div class="panel panel-default"> <div class="panel-heading"> - <h2 class="panel-title">Cross SDKs Configuration</h2> + <h2 class="panel-title" (click)="sdksIsCollapsed = !sdksIsCollapsed"> + Cross SDKs + <div class="pull-right"> + <button class="btn btn-link" (click)="childSdkModal.show(); $event.stopPropagation()"><span class="fa fa-plus fa-size-x2"></span></button> + + <button class="btn btn-link" (click)="sdksIsCollapsed = !sdksIsCollapsed; $event.stopPropagation()"> + <span class="fa fa-big" [ngClass]="{'fa-angle-double-down': sdksIsCollapsed, 'fa-angle-double-right': !sdksIsCollapsed}"></span> + </button> + </div> + </h2> </div> - <div class="panel-body"> + <div class="panel-body" [collapse]="sdksIsCollapsed"> <div class="row col-xs-12"> <sdks-list-accordion [sdks]="(sdks$ | async)"></sdks-list-accordion> </div> @@ -61,34 +76,31 @@ <div class="panel panel-default"> <div class="panel-heading"> - <h2 class="panel-title">Projects Configuration</h2> - </div> - <div class="panel-body"> - <form [formGroup]="addProjectForm" (ngSubmit)="onSubmit()"> - <div class="row "> - <div class="col-xs-2"> - <button class="btn btn-primary" type="submit" [disabled]="!addProjectForm.valid"><i class="fa fa-plus"></i> Add Folder</button> - </div> + <h2 class="panel-title" (click)="projectsIsCollapsed = !projectsIsCollapsed; $event.stopPropagation()"> + Projects + <div class="pull-right"> + <button class="btn btn-link" (click)="childProjectModal.show(); $event.stopPropagation()"><span class="fa fa-plus fa-size-x2"></span></button> - <div class="col-xs-6"> - <label>Folder Path </label> - <input type="text" style="width:70%;" formControlName="path" placeholder="myProject"> - </div> - <div class="col-xs-4"> - <label>Label </label> - <input type="text" formControlName="label" (keyup)="onKeyLabel($event)"> - </div> + <button class="btn btn-link" (click)="projectsIsCollapsed = !projectsIsCollapsed; $event.stopPropagation()"> + <span class="fa fa-big" [ngClass]="{'fa-angle-double-down': projectsIsCollapsed, 'fa-angle-double-right': !projectsIsCollapsed}"></span> + </button> </div> - </form> - + </h2> + </div> + <div class="panel-body" [collapse]="projectsIsCollapsed"> <div class="row col-xs-12"> <projects-list-accordion [projects]="(config$ | async).projects"></projects-list-accordion> </div> </div> </div> +<!-- Modals --> +<project-add-modal #childProjectModal [title]="'Add a new project'"> +</project-add-modal> +<sdk-add-modal #childSdkModal [title]="'Add a new SDK'"> +</sdk-add-modal> <!-- only for debug --> <div *ngIf="false" class="row"> {{config$ | async | json}} -</div>
\ No newline at end of file +</div> diff --git a/webapp/src/app/config/config.component.ts b/webapp/src/app/config/config.component.ts index 7d9931e..b107e81 100644 --- a/webapp/src/app/config/config.component.ts +++ b/webapp/src/app/config/config.component.ts @@ -1,18 +1,16 @@ -import { Component, OnInit } from "@angular/core"; +import { Component, ViewChild, OnInit } from "@angular/core"; import { Observable } from 'rxjs/Observable'; import { FormControl, FormGroup, Validators, FormBuilder } from '@angular/forms'; +import { CollapseModule } from 'ngx-bootstrap/collapse'; -// Import RxJs required methods -import 'rxjs/add/operator/map'; -import 'rxjs/add/operator/filter'; -import 'rxjs/add/operator/debounceTime'; - -import { ConfigService, IConfig, IProject, ProjectType, IxdsAgentPackage } from "../services/config.service"; +import { ConfigService, IConfig, IxdsAgentPackage } from "../services/config.service"; import { XDSServerService, IServerStatus, IXDSAgentInfo } from "../services/xdsserver.service"; import { XDSAgentService, IAgentStatus } from "../services/xdsagent.service"; import { SyncthingService, ISyncThingStatus } from "../services/syncthing.service"; import { AlertService } from "../services/alert.service"; import { ISdk, SdkService } from "../services/sdk.service"; +import { ProjectAddModalComponent } from "../projects/projectAddModal.component"; +import { SdkAddModalComponent } from "../sdks/sdkAddModal.component"; @Component({ templateUrl: './app/config/config.component.html', @@ -23,6 +21,8 @@ import { ISdk, SdkService } from "../services/sdk.service"; // and from http://plnkr.co/edit/vCdjZM?p=preview export class ConfigComponent implements OnInit { + @ViewChild('childProjectModal') childProjectModal: ProjectAddModalComponent; + @ViewChild('childSdkModal') childSdkModal: SdkAddModalComponent; config$: Observable<IConfig>; sdks$: Observable<ISdk[]>; @@ -34,20 +34,20 @@ export class ConfigComponent implements OnInit { userEditedLabel: boolean = false; xdsAgentPackages: IxdsAgentPackage[] = []; + gConfigIsCollapsed: boolean = true; + sdksIsCollapsed: boolean = true; + projectsIsCollapsed: boolean = false; + // TODO replace by reactive FormControl + add validation syncToolUrl: string; xdsAgentUrl: string; xdsAgentRetry: string; - projectsRootDir: string; + projectsRootDir: string; // FIXME: should be remove when projectAddModal will always return full path showApplyBtn = { // Used to show/hide Apply buttons "retry": false, "rootDir": false, }; - addProjectForm: FormGroup; - pathCtrl = new FormControl("", Validators.required); - - constructor( private configSvr: ConfigService, private xdsServerSvr: XDSServerService, @@ -55,14 +55,7 @@ export class ConfigComponent implements OnInit { private stSvr: SyncthingService, private sdkSvr: SdkService, private alert: AlertService, - private fb: FormBuilder ) { - // FIXME implement multi project support - this.curProj = 0; - this.addProjectForm = fb.group({ - path: this.pathCtrl, - label: ["", Validators.nullValidator], - }); } ngOnInit() { @@ -81,20 +74,6 @@ export class ConfigComponent implements OnInit { this.xdsAgentPackages = cfg.xdsAgentPackages; }); - // Auto create label name - this.pathCtrl.valueChanges - .debounceTime(100) - .filter(n => n) - .map(n => "Project_" + n.split('/')[0]) - .subscribe(value => { - if (value && !this.userEditedLabel) { - this.addProjectForm.patchValue({ label: value }); - } - }); - } - - onKeyLabel(event: any) { - this.userEditedLabel = (this.addProjectForm.value.label !== ""); } submitGlobConf(field: string) { @@ -118,21 +97,10 @@ export class ConfigComponent implements OnInit { } xdsAgentRestartConn() { - let aurl = this.xdsAgentUrl; + let aUrl = this.xdsAgentUrl; this.configSvr.syncToolURL = this.syncToolUrl; - this.configSvr.xdsAgentUrl = aurl; + this.configSvr.xdsAgentUrl = aUrl; this.configSvr.loadProjects(); } - onSubmit() { - let formVal = this.addProjectForm.value; - - this.configSvr.addProject({ - label: formVal['label'], - path: formVal['path'], - type: ProjectType.SYNCTHING, - // FIXME: allow to set defaultSdkID from New Project config panel - }); - } - -}
\ No newline at end of file +} diff --git a/webapp/src/app/devel/build/build.component.css b/webapp/src/app/devel/build/build.component.css index 92f953e..695a89b 100644 --- a/webapp/src/app/devel/build/build.component.css +++ b/webapp/src/app/devel/build/build.component.css @@ -33,8 +33,9 @@ width: 10em; } -.fa-size-x2 { +.fa-big { font-size: 18px; + font-weight: bold; } .textarea-scroll { @@ -46,4 +47,8 @@ h2 { font-family: sans-serif; font-variant: small-caps; font-size: x-large; -}
\ No newline at end of file +} + +.panel-heading { + background: aliceblue; +} diff --git a/webapp/src/app/devel/build/build.component.html b/webapp/src/app/devel/build/build.component.html index 7f85aa6..2bcd2c7 100644 --- a/webapp/src/app/devel/build/build.component.html +++ b/webapp/src/app/devel/build/build.component.html @@ -1,8 +1,15 @@ <div class="panel panel-default"> <div class="panel-heading"> - <h2 class="panel-title">Build</h2> + <h2 class="panel-title" (click)="buildIsCollapsed = !buildIsCollapsed"> + Build + <div class="pull-right"> + <button class="btn btn-link" (click)="buildIsCollapsed = !buildIsCollapsed; $event.stopPropagation()"> + <span class="fa fa-big" [ngClass]="{'fa-angle-double-down': buildIsCollapsed, 'fa-angle-double-right': !buildIsCollapsed}"></span> + </button> + </div> + </h2> </div> - <div class="panel-body"> + <div class="panel-body" [collapse]="buildIsCollapsed"> <form [formGroup]="buildForm"> <div class="col-xs-12"> <table class="table table-borderless table-center"> @@ -18,7 +25,7 @@ </tr> <tr> <th>Project root path</th> - <td> <input type="text" disabled style="width:99%;" [value]="curProject && curProject.path"></td> + <td> <input type="text" disabled style="width:99%;" [value]="curProject && curProject.pathClient"></td> </tr> <tr> <th>Sub-path</th> @@ -26,43 +33,43 @@ </tr> <tr> <td colspan="2"> - <accordion> - <accordion-group #group> - <div accordion-heading> - Advanced Settings - <i class="pull-right float-xs-right fa" [ngClass]="{'fa-chevron-down': group.isOpen, 'fa-chevron-right': !group.isOpen}"></i> - </div> + <accordion> + <accordion-group #group> + <div accordion-heading> + Advanced Settings + <i class="pull-right float-xs-right fa" [ngClass]="{'fa-chevron-down': group.isOpen, 'fa-chevron-right': !group.isOpen}"></i> + </div> - <table class="table table-borderless table-in-accordion"> - <tbody> - <tr> - <th>Clean Command</th> - <td> <input type="text" style="width:99%;" formControlName="cmdClean"> </td> - </tr> - <tr> - <th>Pre-Build Command</th> - <td> <input type="text" style="width:99%;" formControlName="cmdPrebuild"> </td> - </tr> - <tr> - <th>Build Command</th> - <td> <input type="text" style="width:99%;" formControlName="cmdBuild"> </td> - </tr> - <tr> - <th>Populate Command</th> - <td> <input type="text" style="width:99%;" formControlName="cmdPopulate"> </td> - </tr> - <tr> - <th>Env variables</th> - <td> <input type="text" style="width:99%;" formControlName="envVars"> </td> - </tr> - <tr *ngIf="debugEnable"> - <th>Args variables</th> - <td> <input type="text" style="width:99%;" formControlName="cmdArgs"> </td> - </tr> - </tbody> - </table> - </accordion-group> - </accordion> + <table class="table table-borderless table-in-accordion"> + <tbody> + <tr> + <th>Clean Command</th> + <td> <input type="text" style="width:99%;" formControlName="cmdClean"> </td> + </tr> + <tr> + <th>Pre-Build Command</th> + <td> <input type="text" style="width:99%;" formControlName="cmdPrebuild"> </td> + </tr> + <tr> + <th>Build Command</th> + <td> <input type="text" style="width:99%;" formControlName="cmdBuild"> </td> + </tr> + <tr> + <th>Populate Command</th> + <td> <input type="text" style="width:99%;" formControlName="cmdPopulate"> </td> + </tr> + <tr> + <th>Env variables</th> + <td> <input type="text" style="width:99%;" formControlName="envVars"> </td> + </tr> + <tr *ngIf="debugEnable"> + <th>Args variables</th> + <td> <input type="text" style="width:99%;" formControlName="cmdArgs"> </td> + </tr> + </tbody> + </table> + </accordion-group> + </accordion> </td> </tr> </tbody> @@ -105,4 +112,4 @@ </div> </div> </div> -</div>
\ No newline at end of file +</div> diff --git a/webapp/src/app/devel/build/build.component.ts b/webapp/src/app/devel/build/build.component.ts index 449c557..48a5824 100644 --- a/webapp/src/app/devel/build/build.component.ts +++ b/webapp/src/app/devel/build/build.component.ts @@ -23,10 +23,10 @@ export class BuildComponent implements OnInit, AfterViewChecked { @Input() curProject: IProject; - buildForm: FormGroup; - subpathCtrl = new FormControl("", Validators.required); - debugEnable: boolean = false; - + public buildForm: FormGroup; + public subpathCtrl = new FormControl("", Validators.required); + public debugEnable: boolean = false; + public buildIsCollapsed: boolean = false; public cmdOutput: string; public cmdInfo: string; @@ -67,7 +67,8 @@ export class BuildComponent implements OnInit, AfterViewChecked { // Command output data tunneling this.xdsSvr.CmdOutput$.subscribe(data => { - this.cmdOutput += data.stdout + "\n"; + this.cmdOutput += data.stdout; + this.cmdOutput += data.stderr; }); // Command exit @@ -219,4 +220,4 @@ export class BuildComponent implements OnInit, AfterViewChecked { private _outputFooter(): string { return "\n"; } -}
\ No newline at end of file +} diff --git a/webapp/src/app/devel/deploy/deploy.component.ts b/webapp/src/app/devel/deploy/deploy.component.ts index 4dba256..e51b7f2 100644 --- a/webapp/src/app/devel/deploy/deploy.component.ts +++ b/webapp/src/app/devel/deploy/deploy.component.ts @@ -37,8 +37,8 @@ export class DeployComponent implements OnInit { ngOnInit() { this.deploying = false; - if (this.curProject && this.curProject.path) { - this.deployForm.patchValue({ wgtFile: this.curProject.path }); + if (this.curProject && this.curProject.pathClient) { + this.deployForm.patchValue({ wgtFile: this.curProject.pathClient }); } } @@ -60,4 +60,4 @@ export class DeployComponent implements OnInit { this.alert.error(msg); }); } -}
\ No newline at end of file +} diff --git a/webapp/src/app/devel/devel.component.css b/webapp/src/app/devel/devel.component.css index 40d6fec..4b03dcb 100644 --- a/webapp/src/app/devel/devel.component.css +++ b/webapp/src/app/devel/devel.component.css @@ -12,3 +12,8 @@ .table-borderless>thead>tr>th { border: none; } + +a.dropdown-item.disabled { + pointer-events:none; + opacity:0.4; +} diff --git a/webapp/src/app/devel/devel.component.html b/webapp/src/app/devel/devel.component.html index feac413..8e71c58 100644 --- a/webapp/src/app/devel/devel.component.html +++ b/webapp/src/app/devel/devel.component.html @@ -7,11 +7,14 @@ <td> <div class="btn-group" dropdown *ngIf="curPrj"> <button dropdownToggle type="button" class="btn btn-primary dropdown-toggle" style="width: 20em;"> - {{curPrj.label}} <span class="caret" style="float: right; margin-top: 8px;"></span> - </button> + {{curPrj.label}} + <span class="caret" style="float: right; margin-top: 8px;"></span> + </button> <ul *dropdownMenu class="dropdown-menu" role="menu"> - <li role="menuitem"><a class="dropdown-item" *ngFor="let prj of (config$ | async)?.projects" (click)="curPrj=prj">{{prj.label}}</a> + <li role="menuitem"><a class="dropdown-item" *ngFor="let prj of (config$ | async)?.projects" [class.disabled]="!prj.isUsable" + (click)="curPrj=prj">{{prj.label}}</a> </li> + </ul> </div> <span *ngIf="!curPrj" style="color:red; font-style: italic;"> @@ -34,4 +37,4 @@ <panel-deploy [curProject]=curPrj></panel-deploy> </div> --> -</div>
\ No newline at end of file +</div> diff --git a/webapp/src/app/devel/devel.component.ts b/webapp/src/app/devel/devel.component.ts index ff12127..f40f25f 100644 --- a/webapp/src/app/devel/devel.component.ts +++ b/webapp/src/app/devel/devel.component.ts @@ -22,11 +22,14 @@ export class DevelComponent { ngOnInit() { this.config$ = this.configSvr.conf; this.config$.subscribe((cfg) => { - if ("projects" in cfg) { + // Select project if no one is selected or no project exists + if (this.curPrj && "id" in this.curPrj) { + this.curPrj = cfg.projects.find(p => p.id === this.curPrj.id) || cfg.projects[0]; + } else if (this.curPrj == null && "projects" in cfg) { this.curPrj = cfg.projects[0]; } else { this.curPrj = null; } }); } -}
\ No newline at end of file +} diff --git a/webapp/src/app/projects/projectAddModal.component.css b/webapp/src/app/projects/projectAddModal.component.css new file mode 100644 index 0000000..77f73a5 --- /dev/null +++ b/webapp/src/app/projects/projectAddModal.component.css @@ -0,0 +1,24 @@ +.table-borderless>tbody>tr>td, +.table-borderless>tbody>tr>th, +.table-borderless>tfoot>tr>td, +.table-borderless>tfoot>tr>th, +.table-borderless>thead>tr>td, +.table-borderless>thead>tr>th { + border: none; +} + +tr>th { + vertical-align: middle; +} + +tr>td { + vertical-align: middle; +} + +th label { + margin-bottom: 0; +} + +td input { + width: 100%; +} diff --git a/webapp/src/app/projects/projectAddModal.component.html b/webapp/src/app/projects/projectAddModal.component.html new file mode 100644 index 0000000..dc84985 --- /dev/null +++ b/webapp/src/app/projects/projectAddModal.component.html @@ -0,0 +1,54 @@ +<div bsModal #childProjectModal="bs-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel" + [config]="{backdrop: 'static'}" aria-hidden="true"> + <div class="modal-dialog modal-lg"> + <div class="modal-content"> + <div class="modal-header"> + <h4 class="modal-title pull-left">{{title}}</h4> + <button type="button" class="close pull-right" aria-label="Close" (click)="hide()"> + <span aria-hidden="true">×</span> + </button> + </div> + + <form [formGroup]="addProjectForm" (ngSubmit)="onSubmit()"> + <div class="modal-body"> + <div class="row "> + <div class="col-xs-12"> + <table class="table table-borderless"> + <tbody> + <tr> + <th><label>Sharing Type </label></th> + <td><select class="form-control" formControlName="type"> + <option *ngFor="let t of projectTypes" [value]="t.value">{{t.display}} + </option> + </select> + </td> + </tr> + <tr> + <th><label for="select-local-path">Local Path </label></th> + <td><input type="text" id="select-local-path" formControlName="pathCli" placeholder="/tmp/myProject" (change)="onChangeLocalProject($event)"></td> + </tr> + <tr> + <th><label for="select-server-path">Server Path </label></th> + <td><input type="text" id="select-server-path" formControlName="pathSvr"></td> + </tr> + <tr> + <th><label for="select-label">Label </label></th> + <td><input type="text" formControlName="label" id="select-label" (keyup)="onKeyLabel($event)"></td> + </tr> + </tbody> + </table> + </div> + </div> + </div> + <div class="modal-footer"> + <div class="pull-left"> + <button class="btn btn-default" (click)="cancelAction=true; hide()"> Cancel </button> + </div> + <div class=""> + <button class="btn btn-primary" type="submit" [disabled]="!addProjectForm.valid">Add Folder</button> + </div> + </div> + </form> + </div> + </div> +</div> diff --git a/webapp/src/app/projects/projectAddModal.component.ts b/webapp/src/app/projects/projectAddModal.component.ts new file mode 100644 index 0000000..7ef5b5e --- /dev/null +++ b/webapp/src/app/projects/projectAddModal.component.ts @@ -0,0 +1,152 @@ +import { Component, Input, ViewChild, OnInit } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import { ModalDirective } from 'ngx-bootstrap/modal'; +import { FormControl, FormGroup, Validators, 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 "../services/alert.service"; +import { + ConfigService, IConfig, IProject, ProjectType, ProjectTypes, + IxdsAgentPackage +} from "../services/config.service"; + + +@Component({ + selector: 'project-add-modal', + templateUrl: './app/projects/projectAddModal.component.html', + styleUrls: ['./app/projects/projectAddModal.component.css'] +}) +export class ProjectAddModalComponent { + @ViewChild('childProjectModal') public childProjectModal: ModalDirective; + @Input() title?: string; + + config$: Observable<IConfig>; + + cancelAction: boolean = false; + userEditedLabel: boolean = false; + projectTypes = ProjectTypes; + + addProjectForm: FormGroup; + typeCtrl: FormControl; + pathCliCtrl: FormControl; + pathSvrCtrl: FormControl; + + constructor( + private alert: AlertService, + private configSvr: ConfigService, + private fb: FormBuilder + ) { + // Define types (first one is special/placeholder) + this.projectTypes.unshift({ value: -1, display: "--Select a type--" }); + + this.typeCtrl = new FormControl(this.projectTypes[0].value, Validators.pattern("[0-9]+")); + this.pathCliCtrl = new FormControl("", Validators.required); + this.pathSvrCtrl = new FormControl({ value: "", disabled: true }, [Validators.required, Validators.minLength(1)]); + + this.addProjectForm = fb.group({ + type: this.typeCtrl, + pathCli: this.pathCliCtrl, + pathSvr: this.pathSvrCtrl, + label: ["", Validators.nullValidator], + }); + } + + ngOnInit() { + this.config$ = this.configSvr.conf; + + // Auto create label name + this.pathCliCtrl.valueChanges + .debounceTime(100) + .filter(n => n) + .map(n => { + let 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 => { + let dis = (valType === String(ProjectType.SYNCTHING)); + this.pathSvrCtrl.reset({ value: "", disabled: dis }); + }); + } + + show() { + this.cancelAction = false; + this.childProjectModal.show(); + } + + hide() { + this.childProjectModal.hide(); + } + + onKeyLabel(event: any) { + this.userEditedLabel = (this.addProjectForm.value.label !== ""); + } + + /* FIXME: change input to file type + <td><input type="file" id="select-local-path" webkitdirectory + formControlName="pathCli" placeholder="myProject" (change)="onChangeLocalProject($event)"></td> + + onChangeLocalProject(e) { + if e.target.files.length < 1 { + console.log('NO files'); + } + let dir = e.target.files[0].webkitRelativePath; + console.log("files: " + dir); + let u = URL.createObjectURL(e.target.files[0]); + } + */ + onChangeLocalProject(e) { + } + + onSubmit() { + if (this.cancelAction) { + return; + } + + let formVal = this.addProjectForm.value; + + let type = formVal['type'].value; + let numType = Number(formVal['type']); + this.configSvr.addProject({ + label: formVal['label'], + pathClient: formVal['pathCli'], + pathServer: formVal['pathSvr'], + type: numType, + // FIXME: allow to set defaultSdkID from New Project config panel + }) + .subscribe(prj => { + this.alert.info("Project " + prj.label + " successfully created."); + this.hide(); + + // Reset Value for the next creation + this.addProjectForm.reset(); + let selectedType = this.projectTypes[0].value; + this.addProjectForm.patchValue({ type: selectedType }); + + }, + err => { + this.alert.error("Configuration ERROR: " + err, 60); + this.hide(); + }); + } + +} diff --git a/webapp/src/app/projects/projectCard.component.ts b/webapp/src/app/projects/projectCard.component.ts index 7a7fa21..a7ca9a3 100644 --- a/webapp/src/app/projects/projectCard.component.ts +++ b/webapp/src/app/projects/projectCard.component.ts @@ -1,5 +1,6 @@ import { Component, Input, Pipe, PipeTransform } from '@angular/core'; import { ConfigService, IProject, ProjectType } from "../services/config.service"; +import { AlertService } from "../services/alert.service"; @Component({ selector: 'project-card', @@ -7,7 +8,9 @@ import { ConfigService, IProject, ProjectType } from "../services/config.service <div class="row"> <div class="col-xs-12"> <div class="text-right" role="group"> - <button class="btn btn-link" (click)="delete(project)"><span class="fa fa-trash fa-size-x2"></span></button> + <button class="btn btn-link" (click)="delete(project)"> + <span class="fa fa-trash fa-size-x2"></span> + </button> </div> </div> </div> @@ -19,14 +22,25 @@ import { ConfigService, IProject, ProjectType } from "../services/config.service <td>{{ project.id }}</td> </tr> <tr> - <th><span class="fa fa-fw fa-folder-open-o"></span> <span>Folder path</span></th> - <td>{{ project.path}}</td> + <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-exchange"></span> <span>Synchronization type</span></th> - <td>{{ project.type | readableType }}</td> + <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-link" (click)="sync(project)"> + <span class="fa fa-refresh fa-size-x2"></span> + </button> + </td> </tr> - </tbody> </table > `, @@ -37,12 +51,26 @@ export class ProjectCardComponent { @Input() project: IProject; - constructor(private configSvr: ConfigService) { + constructor( + private alert: AlertService, + private configSvr: ConfigService + ) { } - delete(prj: IProject) { - this.configSvr.deleteProject(prj); + this.configSvr.deleteProject(prj) + .subscribe(res => { + }, err => { + this.alert.error("Delete local ERROR: " + err); + }); + } + + sync(prj: IProject) { + this.configSvr.syncProject(prj) + .subscribe(res => { + }, err => { + this.alert.error("ERROR: " + err); + }); } } @@ -53,11 +81,11 @@ export class ProjectCardComponent { }) export class ProjectReadableTypePipe implements PipeTransform { - transform(type: ProjectType): string { - switch (+type) { - case ProjectType.NATIVE: return "Native"; - case ProjectType.SYNCTHING: return "Cloud (Syncthing)"; - default: return String(type); + transform(type: ProjectType): string { + switch (type) { + case ProjectType.NATIVE_PATHMAP: return "Native (path mapping)"; + case ProjectType.SYNCTHING: return "Cloud (Syncthing)"; + default: return String(type); + } } - } -}
\ No newline at end of file +} diff --git a/webapp/src/app/projects/projectsListAccordion.component.ts b/webapp/src/app/projects/projectsListAccordion.component.ts index 1b43cea..6e697f4 100644 --- a/webapp/src/app/projects/projectsListAccordion.component.ts +++ b/webapp/src/app/projects/projectsListAccordion.component.ts @@ -5,12 +5,25 @@ import { IProject } from "../services/config.service"; @Component({ selector: 'projects-list-accordion', template: ` + <style> + .fa.fa-exclamation-triangle { + margin-right: 2em; + color: red; + } + .fa.fa-refresh { + margin-right: 10px; + color: darkviolet; + } + </style> <accordion> <accordion-group #group *ngFor="let prj of projects"> <div accordion-heading> {{ prj.label }} - <i class="pull-right float-xs-right fa" - [ngClass]="{'fa-chevron-down': group.isOpen, 'fa-chevron-right': !group.isOpen}"></i> + <div class="pull-right"> + <i *ngIf="prj.status == 'Syncing'" class="fa fa-refresh faa-spin animated"></i> + <i *ngIf="!prj.isInSync && prj.status != 'Syncing'" class="fa fa-exclamation-triangle"></i> + <i class="fa" [ngClass]="{'fa-chevron-down': group.isOpen, 'fa-chevron-right': !group.isOpen}"></i> + </div> </div> <project-card [project]="prj"></project-card> </accordion-group> diff --git a/webapp/src/app/sdks/sdkAddModal.component.html b/webapp/src/app/sdks/sdkAddModal.component.html new file mode 100644 index 0000000..2c07fca --- /dev/null +++ b/webapp/src/app/sdks/sdkAddModal.component.html @@ -0,0 +1,23 @@ +<div bsModal #sdkChildModal="bs-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel" + aria-hidden="true"> + <div class="modal-dialog modal-lg"> + <div class="modal-content"> + <div class="modal-header"> + <h4 class="modal-title pull-left">{{title}}</h4> + <button type="button" class="close pull-right" aria-label="Close" (click)="hideChildModal()"> + <span aria-hidden="true">×</span> + </button> + </div> + <div class="modal-body"> + <ng-content select=".modal-body"> </ng-content> + <i>Not available for now.</i> + </div> + + <div class="modal-footer"> + <div class="pull-left"> + <button class="btn btn-default" (click)="hide()"> Cancel </button> + </div> + </div> + </div> + </div> +</div> diff --git a/webapp/src/app/sdks/sdkAddModal.component.ts b/webapp/src/app/sdks/sdkAddModal.component.ts new file mode 100644 index 0000000..b6c8eb2 --- /dev/null +++ b/webapp/src/app/sdks/sdkAddModal.component.ts @@ -0,0 +1,24 @@ +import { Component, Input, ViewChild } from '@angular/core'; +import { ModalDirective } from 'ngx-bootstrap/modal'; + +@Component({ + selector: 'sdk-add-modal', + templateUrl: './app/sdks/sdkAddModal.component.html', +}) +export class SdkAddModalComponent { + @ViewChild('sdkChildModal') public sdkChildModal: ModalDirective; + + @Input() title?: string; + + // TODO + constructor() { + } + + show() { + this.sdkChildModal.show(); + } + + hide() { + this.sdkChildModal.hide(); + } +} diff --git a/webapp/src/app/services/alert.service.ts b/webapp/src/app/services/alert.service.ts index 9dab36a..c3cae7a 100644 --- a/webapp/src/app/services/alert.service.ts +++ b/webapp/src/app/services/alert.service.ts @@ -30,8 +30,10 @@ export class AlertService { this.uid = 0; } - public error(msg: string) { - this.add({ type: "danger", msg: msg, dismissible: true }); + public error(msg: string, dismissTime?: number) { + this.add({ + type: "danger", msg: msg, dismissible: true, dismissTimeout: dismissTime + }); } public warning(msg: string, dismissible?: boolean) { diff --git a/webapp/src/app/services/config.service.ts b/webapp/src/app/services/config.service.ts index 722c347..4501add 100644 --- a/webapp/src/app/services/config.service.ts +++ b/webapp/src/app/services/config.service.ts @@ -13,28 +13,40 @@ import 'rxjs/add/observable/throw'; import 'rxjs/add/operator/mergeMap'; -import { XDSServerService, IXDSConfigProject } from "../services/xdsserver.service"; +import { XDSServerService, IXDSFolderConfig } from "../services/xdsserver.service"; import { XDSAgentService } from "../services/xdsagent.service"; import { SyncthingService, ISyncThingProject, ISyncThingStatus } from "../services/syncthing.service"; import { AlertService, IAlert } from "../services/alert.service"; import { UtilsService } from "../services/utils.service"; export enum ProjectType { - NATIVE = 1, + NATIVE_PATHMAP = 1, SYNCTHING = 2 } -export interface INativeProject { - // TODO -} +export var ProjectTypes = [ + { value: ProjectType.NATIVE_PATHMAP, display: "Path mapping" }, + { value: ProjectType.SYNCTHING, display: "Cloud Sync" } +]; + +export var ProjectStatus = { + ErrorConfig: "ErrorConfig", + Disable: "Disable", + Enable: "Enable", + Pause: "Pause", + Syncing: "Syncing" +}; export interface IProject { id?: string; label: string; - path: string; + pathClient: string; + pathServer?: string; type: ProjectType; - remotePrjDef?: INativeProject | ISyncThingProject; - localPrjDef?: any; + status?: string; + isInSync?: boolean; + isUsable?: boolean; + serverPrjDef?: IXDSFolderConfig; isExpanded?: boolean; visible?: boolean; defaultSdkID?: string; @@ -133,6 +145,18 @@ export class ConfigService { ); this.confSubject.next(Object.assign({}, this.confStore)); }); + + // Update Project data + this.xdsServerSvr.FolderStateChange$.subscribe(prj => { + let i = this._getProjectIdx(prj.id); + if (i >= 0) { + // XXX for now, only isInSync and status may change + this.confStore.projects[i].isInSync = prj.isInSync; + this.confStore.projects[i].status = prj.status; + this.confStore.projects[i].isUsable = this._isUsableProject(prj); + this.confSubject.next(Object.assign({}, this.confStore)); + } + }); } // Save config into cookie @@ -172,7 +196,7 @@ export class ConfigService { let zurl = this.confStore.xdsAgentPackages && this.confStore.xdsAgentPackages.filter(elem => elem.os === os); if (zurl && zurl.length) { msg += " Download XDS-Agent tarball for " + zurl[0].os + " host OS "; - msg += "<a class=\"fa fa-download\" href=\"" + zurl[0].url + "\" target=\"_blank\"></a>"; + msg += "<a class=\"fa fa-download\" href=\"" + zurl[0].url + "\" target=\"_blank\"></a>"; } msg += "</span>"; this.alert.error(msg); @@ -209,16 +233,8 @@ export class ConfigService { this.stSvr.getProjects().subscribe(localPrj => { remotePrj.forEach(rPrj => { let lPrj = localPrj.filter(item => item.id === rPrj.id); - if (lPrj.length > 0) { - let pp: IProject = { - id: rPrj.id, - label: rPrj.label, - path: rPrj.path, - type: ProjectType.SYNCTHING, // FIXME support other types - remotePrjDef: Object.assign({}, rPrj), - localPrjDef: Object.assign({}, lPrj[0]), - }; - this.confStore.projects.push(pp); + if (lPrj.length > 0 || rPrj.type === ProjectType.NATIVE_PATHMAP) { + this._addProject(rPrj, true); } }); this.confSubject.next(Object.assign({}, this.confStore)); @@ -270,100 +286,133 @@ export class ConfigService { return id.slice(0, 15); } - addProject(prj: IProject) { + addProject(prj: IProject): Observable<IProject> { // Substitute tilde with to user home path - prj.path = prj.path.trim(); - if (prj.path.charAt(0) === '~') { - prj.path = this.confStore.localSThg.tilde + prj.path.substring(1); + let pathCli = prj.pathClient.trim(); + if (pathCli.charAt(0) === '~') { + pathCli = this.confStore.localSThg.tilde + pathCli.substring(1); // Must be a full path (on Linux or Windows) - } else if (!((prj.path.charAt(0) === '/') || - (prj.path.charAt(1) === ':' && (prj.path.charAt(2) === '\\' || prj.path.charAt(2) === '/')))) { - prj.path = this.confStore.projectsRootDir + '/' + prj.path; - } - - if (prj.id == null) { - // FIXME - must be done on server side - let prefix = this.getLabelRootName() || new Date().toISOString(); - let splath = prj.path.split('/'); - prj.id = prefix + "_" + splath[splath.length - 1]; - } - - if (this._getProjectIdx(prj.id) !== -1) { - this.alert.warning("Project already exist (id=" + prj.id + ")", true); - return; - } - - // TODO - support others project types - if (prj.type !== ProjectType.SYNCTHING) { - this.alert.error('Project type not supported yet (type: ' + prj.type + ')'); - return; + } else if (!((pathCli.charAt(0) === '/') || + (pathCli.charAt(1) === ':' && (pathCli.charAt(2) === '\\' || pathCli.charAt(2) === '/')))) { + pathCli = this.confStore.projectsRootDir + '/' + pathCli; } - let sdkPrj: IXDSConfigProject = { - id: prj.id, - label: prj.label, - path: prj.path, - hostSyncThingID: this.confStore.localSThg.ID, + let xdsPrj: IXDSFolderConfig = { + id: "", + label: prj.label || "", + path: pathCli, + type: prj.type, defaultSdkID: prj.defaultSdkID, + dataPathMap: { + serverPath: prj.pathServer, + }, + dataCloudSync: { + syncThingID: this.confStore.localSThg.ID, + } }; - // Send config to XDS server let newPrj = prj; - this.xdsServerSvr.addProject(sdkPrj) - .subscribe(resStRemotePrj => { - newPrj.remotePrjDef = resStRemotePrj; - - // FIXME REWORK local ST config - // move logic to server side tunneling-back by WS - - // Now setup local config - let stLocPrj: ISyncThingProject = { - id: sdkPrj.id, - label: sdkPrj.label, - path: sdkPrj.path, - remoteSyncThingID: resStRemotePrj.builderSThgID - }; - - // Set local Syncthing config - this.stSvr.addProject(stLocPrj) - .subscribe(resStLocalPrj => { - newPrj.localPrjDef = resStLocalPrj; - - // FIXME: maybe reduce subject to only .project - //this.confSubject.next(Object.assign({}, this.confStore).project); - this.confStore.projects.push(Object.assign({}, newPrj)); - this.confSubject.next(Object.assign({}, this.confStore)); - }, - err => { - this.alert.error("Configuration local ERROR: " + err); - }); - }, - err => { - this.alert.error("Configuration remote ERROR: " + err); + return this.xdsServerSvr.addProject(xdsPrj) + .flatMap(resStRemotePrj => { + xdsPrj = resStRemotePrj; + if (xdsPrj.type === ProjectType.SYNCTHING) { + // FIXME REWORK local ST config + // move logic to server side tunneling-back by WS + let stData = xdsPrj.dataCloudSync; + + // Now setup local config + let stLocPrj: ISyncThingProject = { + id: xdsPrj.id, + label: xdsPrj.label, + path: xdsPrj.path, + serverSyncThingID: stData.builderSThgID + }; + + // Set local Syncthing config + return this.stSvr.addProject(stLocPrj); + + } else { + return Observable.of(null); + } + }) + .map(resStLocalPrj => { + this._addProject(xdsPrj); + return newPrj; }); } - deleteProject(prj: IProject) { + deleteProject(prj: IProject): Observable<IProject> { let idx = this._getProjectIdx(prj.id); + let delPrj = prj; if (idx === -1) { throw new Error("Invalid project id (id=" + prj.id + ")"); } - this.xdsServerSvr.deleteProject(prj.id) - .subscribe(res => { - this.stSvr.deleteProject(prj.id) - .subscribe(res => { - this.confStore.projects.splice(idx, 1); - }, err => { - this.alert.error("Delete local ERROR: " + err); - }); - }, err => { - this.alert.error("Delete remote ERROR: " + err); + return this.xdsServerSvr.deleteProject(prj.id) + .flatMap(res => { + if (prj.type === ProjectType.SYNCTHING) { + return this.stSvr.deleteProject(prj.id); + } + return Observable.of(null); + }) + .map(res => { + this.confStore.projects.splice(idx, 1); + return delPrj; }); } + syncProject(prj: IProject): Observable<string> { + let idx = this._getProjectIdx(prj.id); + if (idx === -1) { + throw new Error("Invalid project id (id=" + prj.id + ")"); + } + return this.xdsServerSvr.syncProject(prj.id); + } + + private _isUsableProject(p) { + return p && p.isInSync && + (p.status === ProjectStatus.Enable) && + (p.status !== ProjectStatus.Syncing); + } + private _getProjectIdx(id: string): number { return this.confStore.projects.findIndex((item) => item.id === id); } -}
\ No newline at end of file + private _addProject(rPrj: IXDSFolderConfig, noNext?: boolean) { + + // Convert XDSFolderConfig to IProject + let pp: IProject = { + id: rPrj.id, + label: rPrj.label, + pathClient: rPrj.path, + pathServer: rPrj.dataPathMap.serverPath, + type: rPrj.type, + status: rPrj.status, + isInSync: rPrj.isInSync, + isUsable: this._isUsableProject(rPrj), + defaultSdkID: rPrj.defaultSdkID, + serverPrjDef: Object.assign({}, rPrj), // do a copy + }; + + // add new project + this.confStore.projects.push(pp); + + // sort project array + this.confStore.projects.sort((a, b) => { + if (a.label < b.label) { + return -1; + } + if (a.label > b.label) { + return 1; + } + return 0; + }); + + // FIXME: maybe reduce subject to only .project + //this.confSubject.next(Object.assign({}, this.confStore).project); + if (!noNext) { + this.confSubject.next(Object.assign({}, this.confStore)); + } + } +} diff --git a/webapp/src/app/services/syncthing.service.ts b/webapp/src/app/services/syncthing.service.ts index 0e8c51c..aefb039 100644 --- a/webapp/src/app/services/syncthing.service.ts +++ b/webapp/src/app/services/syncthing.service.ts @@ -16,7 +16,7 @@ import 'rxjs/add/operator/retryWhen'; export interface ISyncThingProject { id: string; path: string; - remoteSyncThingID: string; + serverSyncThingID: string; label?: string; } @@ -180,7 +180,7 @@ export class SyncthingService { return this.getID() .flatMap(() => this._getConfig()) .flatMap((stCfg) => { - let newDevID = prj.remoteSyncThingID; + let newDevID = prj.serverSyncThingID; // Add new Device if needed let dev = stCfg.devices.filter(item => item.deviceID === newDevID); diff --git a/webapp/src/app/services/xdsserver.service.ts b/webapp/src/app/services/xdsserver.service.ts index 4d20fa4..b69a196 100644 --- a/webapp/src/app/services/xdsserver.service.ts +++ b/webapp/src/app/services/xdsserver.service.ts @@ -20,7 +20,8 @@ import 'rxjs/add/operator/mergeMap'; export interface IXDSConfigProject { id: string; path: string; - hostSyncThingID: string; + clientSyncThingID: string; + type: number; label?: string; defaultSdkID?: string; } @@ -31,15 +32,29 @@ interface IXDSBuilderConfig { syncThingID: string; } -interface IXDSFolderConfig { +export interface IXDSFolderConfig { id: string; label: string; path: string; type: number; - syncThingID: string; - builderSThgID?: string; status?: string; + isInSync?: boolean; defaultSdkID: string; + + // FIXME better with union but tech pb with go code + //data?: IXDSPathMapConfig|IXDSCloudSyncConfig; + dataPathMap?: IXDSPathMapConfig; + dataCloudSync?: IXDSCloudSyncConfig; +} + +export interface IXDSPathMapConfig { + // TODO + serverPath: string; +} + +export interface IXDSCloudSyncConfig { + syncThingID: string; + builderSThgID?: string; } interface IXDSConfig { @@ -92,8 +107,10 @@ export class XDSServerService { public CmdOutput$ = <Subject<ICmdOutput>>new Subject(); public CmdExit$ = <Subject<ICmdExit>>new Subject(); + public FolderStateChange$ = <Subject<IXDSFolderConfig>>new Subject(); public Status$: Observable<IServerStatus>; + private baseUrl: string; private wsUrl: string; private _status = { WS_connected: false }; @@ -113,6 +130,7 @@ export class XDSServerService { } else { this.wsUrl = 'ws://' + re[1]; this._handleIoSocket(); + this._RegisterEvents(); } } @@ -158,6 +176,22 @@ export class XDSServerService { this.CmdExit$.next(Object.assign({}, <ICmdExit>data)); }); + this.socket.on('event:FolderStateChanged', ev => { + if (ev && ev.folder) { + this.FolderStateChange$.next(Object.assign({}, ev.folder)); + } + }); + } + + private _RegisterEvents() { + let ev = "FolderStateChanged"; + this._post('/events/register', { "name": ev }) + .subscribe( + res => { }, + error => { + this.alert.error("ERROR while registering events " + ev + ": ", error); + } + ); } getSdks(): Observable<ISdk[]> { @@ -172,22 +206,18 @@ export class XDSServerService { return this._get('/folders'); } - addProject(cfg: IXDSConfigProject): Observable<IXDSFolderConfig> { - let folder: IXDSFolderConfig = { - id: cfg.id || null, - label: cfg.label || "", - path: cfg.path, - type: FOLDER_TYPE_CLOUDSYNC, - syncThingID: cfg.hostSyncThingID, - defaultSdkID: cfg.defaultSdkID || "", - }; - return this._post('/folder', folder); + addProject(cfg: IXDSFolderConfig): Observable<IXDSFolderConfig> { + return this._post('/folder', cfg); } deleteProject(id: string): Observable<IXDSFolderConfig> { return this._delete('/folder/' + id); } + syncProject(id: string): Observable<string> { + return this._post('/folder/sync/' + id, {}); + } + exec(prjID: string, dir: string, cmd: string, sdkid?: string, args?: string[], env?: string[]): Observable<any> { return this._post('/exec', { @@ -244,7 +274,13 @@ export class XDSServerService { private _decodeError(err: any) { let e: string; - if (typeof err === "object") { + if (err instanceof Response) { + const body = err.json() || 'Server error'; + e = body.error || JSON.stringify(body); + if (!e || e === "") { + e = `${err.status} - ${err.statusText || 'Unknown error'}`; + } + } else if (typeof err === "object") { if (err.statusText) { e = err.statusText; } else if (err.error) { @@ -253,7 +289,7 @@ export class XDSServerService { e = JSON.stringify(err); } } else { - e = err.json().error || 'Server error'; + e = err.message ? err.message : err.toString(); } return Observable.throw(e); } diff --git a/webapp/src/index.html b/webapp/src/index.html index 33e5efd..290b4be 100644 --- a/webapp/src/index.html +++ b/webapp/src/index.html @@ -40,10 +40,11 @@ <body style="padding-top: 70px;"> <!-- padding needed due to fixed navbar --> <app> <div style="text-align:center; position:absolute; top:50%; width:100%; transform:translate(0,-50%);"> - Loading... + <img id="logo-iot" src="assets/images/iot-bzh-logo-small.png"> + <br> Loading... <i class="fa fa-spinner fa-spin fa-fw"></i> </div> </app> </body> -</html>
\ No newline at end of file +</html> diff --git a/webapp/src/systemjs.config.js b/webapp/src/systemjs.config.js index 19fe225..15c52ba 100644 --- a/webapp/src/systemjs.config.js +++ b/webapp/src/systemjs.config.js @@ -39,6 +39,7 @@ 'ngx-bootstrap/carousel': 'npm:ngx-bootstrap/bundles/ngx-bootstrap.umd.min.js', 'ngx-bootstrap/popover': 'npm:ngx-bootstrap/bundles/ngx-bootstrap.umd.min.js', 'ngx-bootstrap/dropdown': 'npm:ngx-bootstrap/bundles/ngx-bootstrap.umd.min.js', + 'ngx-bootstrap/collapse': 'npm:ngx-bootstrap/bundles/ngx-bootstrap.umd.min.js', // other libraries 'socket.io-client': 'npm:socket.io-client/dist/socket.io.min.js' }, @@ -65,4 +66,4 @@ } } }); -})(this);
\ No newline at end of file +})(this); |