summaryrefslogtreecommitdiffstats
path: root/webapp
diff options
context:
space:
mode:
Diffstat (limited to 'webapp')
-rw-r--r--webapp/assets/images/iot-bzh-logo-small.pngbin0 -> 14449 bytes
-rw-r--r--webapp/src/app/app.component.css22
-rw-r--r--webapp/src/app/app.component.html7
-rw-r--r--webapp/src/app/app.module.ts8
-rw-r--r--webapp/src/app/config/config.component.css11
-rw-r--r--webapp/src/app/config/config.component.html66
-rw-r--r--webapp/src/app/config/config.component.ts62
-rw-r--r--webapp/src/app/devel/build/build.component.css9
-rw-r--r--webapp/src/app/devel/build/build.component.html87
-rw-r--r--webapp/src/app/devel/build/build.component.ts13
-rw-r--r--webapp/src/app/devel/deploy/deploy.component.ts6
-rw-r--r--webapp/src/app/devel/devel.component.css5
-rw-r--r--webapp/src/app/devel/devel.component.html11
-rw-r--r--webapp/src/app/devel/devel.component.ts7
-rw-r--r--webapp/src/app/projects/projectAddModal.component.css24
-rw-r--r--webapp/src/app/projects/projectAddModal.component.html54
-rw-r--r--webapp/src/app/projects/projectAddModal.component.ts152
-rw-r--r--webapp/src/app/projects/projectCard.component.ts60
-rw-r--r--webapp/src/app/projects/projectsListAccordion.component.ts17
-rw-r--r--webapp/src/app/sdks/sdkAddModal.component.html23
-rw-r--r--webapp/src/app/sdks/sdkAddModal.component.ts24
-rw-r--r--webapp/src/app/services/alert.service.ts6
-rw-r--r--webapp/src/app/services/config.service.ts235
-rw-r--r--webapp/src/app/services/syncthing.service.ts4
-rw-r--r--webapp/src/app/services/xdsserver.service.ts68
-rw-r--r--webapp/src/index.html5
-rw-r--r--webapp/src/systemjs.config.js3
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
new file mode 100644
index 0000000..2c3b2ae
--- /dev/null
+++ b/webapp/assets/images/iot-bzh-logo-small.png
Binary files differ
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>&nbsp;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">&times;</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>&nbsp;<span>Folder path</span></th>
- <td>{{ project.path}}</td>
+ <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-exchange"></span>&nbsp;<span>Synchronization type</span></th>
- <td>{{ project.type | readableType }}</td>
+ <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-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">&times;</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);