diff options
Diffstat (limited to 'webapp')
-rw-r--r-- | webapp/assets/xds-agent-tarballs/.gitkeep | 0 | ||||
-rw-r--r-- | webapp/src/app/alert/alert.component.ts | 2 | ||||
-rw-r--r-- | webapp/src/app/app.module.ts | 4 | ||||
-rw-r--r-- | webapp/src/app/common/alert.service.ts | 2 | ||||
-rw-r--r-- | webapp/src/app/common/config.service.ts | 75 | ||||
-rw-r--r-- | webapp/src/app/common/utils.service.ts | 23 | ||||
-rw-r--r-- | webapp/src/app/common/xdsagent.service.ts | 213 | ||||
-rw-r--r-- | webapp/src/app/common/xdsserver.service.ts | 13 | ||||
-rw-r--r-- | webapp/src/app/config/config.component.html | 25 | ||||
-rw-r--r-- | webapp/src/app/config/config.component.ts | 32 |
10 files changed, 362 insertions, 27 deletions
diff --git a/webapp/assets/xds-agent-tarballs/.gitkeep b/webapp/assets/xds-agent-tarballs/.gitkeep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/webapp/assets/xds-agent-tarballs/.gitkeep diff --git a/webapp/src/app/alert/alert.component.ts b/webapp/src/app/alert/alert.component.ts index e9d7629..449506f 100644 --- a/webapp/src/app/alert/alert.component.ts +++ b/webapp/src/app/alert/alert.component.ts @@ -9,7 +9,7 @@ import {AlertService, IAlert} from '../common/alert.service'; <div style="width:80%; margin-left:auto; margin-right:auto;" *ngFor="let alert of (alerts$ | async)"> <alert *ngIf="alert.show" [type]="alert.type" [dismissible]="alert.dismissible" [dismissOnTimeout]="alert.dismissTimeout" (onClose)="onClose(alert)"> - <span [innerHtml]="alert.msg"></span> + <div style="text-align:center;" [innerHtml]="alert.msg"></div> </alert> </div> ` diff --git a/webapp/src/app/app.module.ts b/webapp/src/app/app.module.ts index d4a6918..1abcf0c 100644 --- a/webapp/src/app/app.module.ts +++ b/webapp/src/app/app.module.ts @@ -26,9 +26,11 @@ import { SdkSelectDropdownComponent } from "./sdks/sdkSelectDropdown.component"; import { HomeComponent } from "./home/home.component"; import { BuildComponent } from "./build/build.component"; import { XDSServerService } from "./common/xdsserver.service"; +import { XDSAgentService } from "./common/xdsagent.service"; import { SyncthingService } from "./common/syncthing.service"; import { ConfigService } from "./common/config.service"; import { AlertService } from './common/alert.service'; +import { UtilsService } from './common/utils.service'; import { SdkService } from "./common/sdk.service"; @@ -67,9 +69,11 @@ import { SdkService } from "./common/sdk.service"; useValue: window }, XDSServerService, + XDSAgentService, ConfigService, SyncthingService, AlertService, + UtilsService, SdkService, ], bootstrap: [AppComponent] diff --git a/webapp/src/app/common/alert.service.ts b/webapp/src/app/common/alert.service.ts index 710046f..9dab36a 100644 --- a/webapp/src/app/common/alert.service.ts +++ b/webapp/src/app/common/alert.service.ts @@ -39,7 +39,7 @@ export class AlertService { } public info(msg: string) { - this.add({ type: "warning", msg: msg, dismissible: true, dismissTimeout: this.defaultDissmissTmo }); + this.add({ type: "info", msg: msg, dismissible: true, dismissTimeout: this.defaultDissmissTmo }); } public add(al: IAlert) { diff --git a/webapp/src/app/common/config.service.ts b/webapp/src/app/common/config.service.ts index 201ee8b..a04ac13 100644 --- a/webapp/src/app/common/config.service.ts +++ b/webapp/src/app/common/config.service.ts @@ -14,8 +14,10 @@ import 'rxjs/add/operator/mergeMap'; import { XDSServerService, IXDSConfigProject } from "../common/xdsserver.service"; +import { XDSAgentService } from "../common/xdsagent.service"; import { SyncthingService, ISyncThingProject, ISyncThingStatus } from "../common/syncthing.service"; import { AlertService, IAlert } from "../common/alert.service"; +import { UtilsService } from "../common/utils.service"; export enum ProjectType { NATIVE = 1, @@ -38,6 +40,11 @@ export interface IProject { defaultSdkID?: string; } +export interface IXDSAgentConfig { + URL: string; + retry: number; +} + export interface ILocalSTConfig { ID: string; URL: string; @@ -47,6 +54,8 @@ export interface ILocalSTConfig { export interface IConfig { xdsServerURL: string; + xdsAgent: IXDSAgentConfig; + xdsAgentZipUrl: string; projectsRootDir: string; projects: IProject[]; localSThg: ILocalSTConfig; @@ -59,13 +68,17 @@ export class ConfigService { private confSubject: BehaviorSubject<IConfig>; private confStore: IConfig; + private AgentConnectObs = null; private stConnectObs = null; + private xdsAgentZipUrl = ""; constructor(private _window: Window, private cookie: CookieService, - private sdkSvr: XDSServerService, + private xdsServerSvr: XDSServerService, + private xdsAgentSvr: XDSAgentService, private stSvr: SyncthingService, private alert: AlertService, + private utils: UtilsService, ) { this.load(); this.confSubject = <BehaviorSubject<IConfig>>new BehaviorSubject(this.confStore); @@ -85,6 +98,11 @@ export class ConfigService { // Set default config this.confStore = { xdsServerURL: this._window.location.origin + '/api/v1', + xdsAgent: { + URL: 'http://localhost:8000', + retry: 10, + }, + xdsAgentZipUrl: "", projectsRootDir: "", projects: [], localSThg: { @@ -95,6 +113,13 @@ export class ConfigService { } }; } + + // Update XDS Agent tarball url + this.xdsServerSvr.getXdsAgentInfo().subscribe(nfo => { + let os = this.utils.getOSName(true); + let zurl = nfo.tarballs.filter(elem => elem.os === os); + this.confStore.xdsAgentZipUrl = zurl && zurl[0].fileUrl; + }); } // Save config into cookie @@ -104,11 +129,26 @@ export class ConfigService { // Don't save projects in cookies (too big!) let cfg = this.confStore; - delete(cfg.projects); + delete (cfg.projects); this.cookie.putObject("xds-config", cfg); } loadProjects() { + // Setup connection with local XDS agent + if (this.AgentConnectObs) { + try { + this.AgentConnectObs.unsubscribe(); + } catch (err) { } + this.AgentConnectObs = null; + } + + let cfg = this.confStore.xdsAgent; + this.AgentConnectObs = this.xdsAgentSvr.connect(cfg.retry, cfg.URL) + .subscribe((sts) => { + console.log("Agent sts", sts); + }, error => this.alert.error(error) + ); + // Remove previous subscriber if existing if (this.stConnectObs) { try { @@ -117,7 +157,8 @@ export class ConfigService { this.stConnectObs = null; } - // First setup connection with local SyncThing + // FIXME: move this code and all logic about syncthing inside XDS Agent + // Setup connection with local SyncThing let retry = this.confStore.localSThg.retry; let url = this.confStore.localSThg.URL; this.stConnectObs = this.stSvr.connect(retry, url).subscribe((sts) => { @@ -130,7 +171,7 @@ export class ConfigService { // Rebuild projects definition from local and remote syncthing this.confStore.projects = []; - this.sdkSvr.getProjects().subscribe(remotePrj => { + this.xdsServerSvr.getProjects().subscribe(remotePrj => { this.stSvr.getProjects().subscribe(localPrj => { remotePrj.forEach(rPrj => { let lPrj = localPrj.filter(item => item.id === rPrj.id); @@ -150,7 +191,18 @@ export class ConfigService { }), error => this.alert.error('Could not load initial state of local projects.'); }), error => this.alert.error('Could not load initial state of remote projects.'); - }, error => this.alert.error(error)); + }, error => { + if (error.indexOf("Syncthing local daemon not responding") !== -1) { + let msg = "<span><strong>" + error + "<br></strong>"; + msg += "You may need to download and execute XDS-Agent.<br>"; + msg += "<a class=\"fa fa-download\" href=\"" + this.confStore.xdsAgentZipUrl + "\" target=\"_blank\"></a>"; + msg += " Download XDS-Agent tarball."; + msg += "</span>"; + this.alert.error(msg); + } else { + this.alert.error(error); + } + }); } set syncToolURL(url: string) { @@ -158,11 +210,18 @@ export class ConfigService { this.save(); } - set syncToolRetry(r: number) { + set xdsAgentRetry(r: number) { this.confStore.localSThg.retry = r; + this.confStore.xdsAgent.retry = r; this.save(); } + set xdsAgentUrl(url: string) { + this.confStore.xdsAgent.URL = url; + this.save(); + } + + set projectsRootDir(p: string) { if (p.charAt(0) === '~') { p = this.confStore.localSThg.tilde + p.substring(1); @@ -219,7 +278,7 @@ export class ConfigService { // Send config to XDS server let newPrj = prj; - this.sdkSvr.addProject(sdkPrj) + this.xdsServerSvr.addProject(sdkPrj) .subscribe(resStRemotePrj => { newPrj.remotePrjDef = resStRemotePrj; @@ -258,7 +317,7 @@ export class ConfigService { if (idx === -1) { throw new Error("Invalid project id (id=" + prj.id + ")"); } - this.sdkSvr.deleteProject(prj.id) + this.xdsServerSvr.deleteProject(prj.id) .subscribe(res => { this.stSvr.deleteProject(prj.id) .subscribe(res => { diff --git a/webapp/src/app/common/utils.service.ts b/webapp/src/app/common/utils.service.ts new file mode 100644 index 0000000..291ffd3 --- /dev/null +++ b/webapp/src/app/common/utils.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@angular/core'; + +@Injectable() +export class UtilsService { + constructor() { } + + getOSName(lowerCase?: boolean): string { + let OSName = "Unknown OS"; + if (navigator.appVersion.indexOf("Linux") !== -1) { + OSName = "Linux"; + } else if (navigator.appVersion.indexOf("Win") !== -1) { + OSName = "Windows"; + } else if (navigator.appVersion.indexOf("Mac") !== -1) { + OSName = "MacOS"; + } else if (navigator.appVersion.indexOf("X11") !== -1) { + OSName = "UNIX"; + } + if (lowerCase) { + return OSName.toLowerCase(); + } + return OSName; + } +}
\ No newline at end of file diff --git a/webapp/src/app/common/xdsagent.service.ts b/webapp/src/app/common/xdsagent.service.ts new file mode 100644 index 0000000..4d9aadc --- /dev/null +++ b/webapp/src/app/common/xdsagent.service.ts @@ -0,0 +1,213 @@ +import { Injectable } from '@angular/core'; +import { Http, Headers, RequestOptionsArgs, Response } from '@angular/http'; +import { Location } from '@angular/common'; +import { Observable } from 'rxjs/Observable'; +import { Subject } from 'rxjs/Subject'; +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; +import * as io from 'socket.io-client'; + +import { AlertService } from './alert.service'; + + +// Import RxJs required methods +import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/catch'; +import 'rxjs/add/observable/throw'; + +export interface IXDSVersion { + version: string; + apiVersion: string; + gitTag: string; + +} + +export interface IAgentStatus { + baseURL: string; + connected: boolean; + WS_connected: boolean; + connectionRetry: number; + version: string; +} + +// Default settings +const DEFAULT_PORT = 8010; +const DEFAULT_API_KEY = "1234abcezam"; +const API_VERSION = "v1"; + +@Injectable() +export class XDSAgentService { + public Status$: Observable<IAgentStatus>; + + private baseRestUrl: string; + private wsUrl: string; + private connectionMaxRetry: number; + private apikey: string; + private _status: IAgentStatus = { + baseURL: "", + connected: false, + WS_connected: false, + connectionRetry: 0, + version: "", + }; + private statusSubject = <BehaviorSubject<IAgentStatus>>new BehaviorSubject(this._status); + + + private socket: SocketIOClient.Socket; + + constructor(private http: Http, private _window: Window, private alert: AlertService) { + + this.Status$ = this.statusSubject.asObservable(); + + this.apikey = DEFAULT_API_KEY; // FIXME Add dynamic allocated key + this._status.baseURL = 'http://localhost:' + DEFAULT_PORT; + this.baseRestUrl = this._status.baseURL + '/api/' + API_VERSION; + let re = this._window.location.origin.match(/http[s]?:\/\/([^\/]*)[\/]?/); + if (re === null || re.length < 2) { + console.error('ERROR: cannot determine Websocket url'); + } else { + this.wsUrl = 'ws://' + re[1]; + } + } + + connect(retry: number, url?: string): Observable<IAgentStatus> { + if (url) { + this._status.baseURL = url; + this.baseRestUrl = this._status.baseURL + '/api/' + API_VERSION; + } + //FIXME [XDS-Agent]: not implemented yet, set always as connected + //this._status.connected = false; + this._status.connected = true; + this._status.connectionRetry = 0; + this.connectionMaxRetry = retry || 3600; // 1 hour + + // Init IO Socket connection + this._handleIoSocket(); + + // Get Version in order to check connection via a REST request + return this.getVersion() + .map((v) => { + this._status.version = v.version; + this.statusSubject.next(Object.assign({}, this._status)); + return this._status; + }); + } + + public getVersion(): Observable<IXDSVersion> { + /*FIXME [XDS-Agent]: Not implemented for now + return this._get('/version'); + */ + return Observable.of({ + version: "NOT_IMPLEMENTED", + apiVersion: "NOT_IMPLEMENTED", + gitTag: "NOT_IMPLEMENTED" + }); + } + + private _WSState(sts: boolean) { + this._status.WS_connected = sts; + this.statusSubject.next(Object.assign({}, this._status)); + } + + private _handleIoSocket() { + this.socket = io(this.wsUrl, { transports: ['websocket'] }); + + this.socket.on('connect_error', (res) => { + this._WSState(false); + console.error('WS Connect_error ', res); + }); + + this.socket.on('connect', (res) => { + this._WSState(true); + }); + + this.socket.on('disconnection', (res) => { + this._WSState(false); + this.alert.error('WS disconnection: ' + res); + }); + + this.socket.on('error', (err) => { + console.error('WS error:', err); + }); + + } + + private _attachAuthHeaders(options?: any) { + options = options || {}; + let headers = options.headers || new Headers(); + // headers.append('Authorization', 'Basic ' + btoa('username:password')); + headers.append('Access-Control-Allow-Origin', '*'); + headers.append('Accept', 'application/json'); + headers.append('Content-Type', 'application/json'); + if (this.apikey !== "") { + headers.append('X-API-Key', this.apikey); + + } + + options.headers = headers; + return options; + } + + private _checkAlive(): Observable<boolean> { + if (this._status.connected) { + return Observable.of(true); + } + + return this.http.get(this._status.baseURL, this._attachAuthHeaders()) + .map((r) => this._status.connected = true) + .retryWhen((attempts) => { + this._status.connectionRetry = 0; + return attempts.flatMap(error => { + this._status.connected = false; + if (++this._status.connectionRetry >= this.connectionMaxRetry) { + return Observable.throw("XDS local Agent not responding (url=" + this._status.baseURL + ")"); + } else { + return Observable.timer(1000); + } + }); + }); + } + + private _get(url: string): Observable<any> { + return this._checkAlive() + .flatMap(() => this.http.get(this.baseRestUrl + url, this._attachAuthHeaders())) + .map((res: Response) => res.json()) + .catch(this._decodeError); + } + private _post(url: string, body: any): Observable<any> { + return this._checkAlive() + .flatMap(() => this.http.post(this.baseRestUrl + url, JSON.stringify(body), this._attachAuthHeaders())) + .map((res: Response) => res.json()) + .catch((error) => { + return this._decodeError(error); + }); + } + private _delete(url: string): Observable<any> { + return this._checkAlive() + .flatMap(() => this.http.delete(this.baseRestUrl + url, this._attachAuthHeaders())) + .map((res: Response) => res.json()) + .catch(this._decodeError); + } + + private _decodeError(err: Response | any) { + let e: string; + if (this._status) { + this._status.connected = false; + } + if (typeof err === "object") { + if (err.statusText) { + e = err.statusText; + } else if (err.error) { + e = String(err.error); + } else { + e = JSON.stringify(err); + } + } else if (err instanceof Response) { + const body = err.json() || 'Server error'; + const error = body.error || JSON.stringify(body); + e = `${err.status} - ${err.statusText || ''} ${error}`; + } else { + e = err.message ? err.message : err.toString(); + } + return Observable.throw(e); + } +} diff --git a/webapp/src/app/common/xdsserver.service.ts b/webapp/src/app/common/xdsserver.service.ts index 6cd9ba3..49c2d37 100644 --- a/webapp/src/app/common/xdsserver.service.ts +++ b/webapp/src/app/common/xdsserver.service.ts @@ -48,6 +48,15 @@ interface IXDSConfig { folders: IXDSFolderConfig[]; } +export interface IXDSAgentTarball { + os: string; + fileUrl: string; +} + +export interface IXDSAgentInfo { + tarballs: IXDSAgentTarball[]; +} + export interface ISdkMessage { wsID: string; msgType: string; @@ -144,6 +153,10 @@ export class XDSServerService { return this._get('/sdks'); } + getXdsAgentInfo(): Observable<IXDSAgentInfo> { + return this._get('/xdsagent/info'); + } + getProjects(): Observable<IXDSFolderConfig[]> { return this._get('/folders'); } diff --git a/webapp/src/app/config/config.component.html b/webapp/src/app/config/config.component.html index 2a3d322..77d90c5 100644 --- a/webapp/src/app/config/config.component.html +++ b/webapp/src/app/config/config.component.html @@ -2,7 +2,7 @@ <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]="((severStatus$ | async)?.WS_connected)?'green':'red'"></span> + <span class="fa fa-fw fa-exchange fa-size-x2" [style.color]="((serverStatus$ | async)?.WS_connected)?'green':'red'"></span> </div> </div> <div class="panel-body"> @@ -10,20 +10,33 @@ <div class="col-xs-12"> <table class="table table-condensed"> <tbody> + <!-- FIXME [XDS-Agent] + <tr [ngClass]="{'info': (agentStatus$ | async)?.connected, 'danger': !(agentStatus$ | async)?.connected}"> + --> <tr [ngClass]="{'info': (localSTStatus$ | async)?.connected, 'danger': !(localSTStatus$ | async)?.connected}"> - <th><label>Local Sync-tool URL</label></th> - <td> <input type="text" [(ngModel)]="syncToolUrl"></td> + <th><label>XDS local Agent URL</label></th> + <td> <input type="text" [(ngModel)]="xdsAgentUrl"></td> <td> - <button class="btn btn-link" (click)="syncToolRestartConn()"><span class="fa fa-refresh fa-size-x2"></span></button> + <button class="btn btn-link" (click)="xdsAgentRestartConn()"><span class="fa fa-refresh fa-size-x2"></span></button> + <!-- FIXME [XDS-Agent] + <button *ngIf="!(agentStatus$ | async)?.connected" --> + <button *ngIf="!(localSTStatus$ | async)?.connected" class="btn btn-link"><a class="fa fa-download fa-size-x2" [href]="xdsAgentZipUrl" target="_blank"></a></button> </td> </tr> <tr class="info"> - <th><label>Local Sync-tool connection retry</label></th> - <td> <input type="text" [(ngModel)]="syncToolRetry" (ngModelChange)="showApplyBtn['retry'] = true"></td> + <th><label>Local Agent connection retry</label></th> + <td> <input type="text" [(ngModel)]="xdsAgentRetry" (ngModelChange)="showApplyBtn['retry'] = true"></td> <td> <button *ngIf="showApplyBtn['retry']" class="btn btn-primary btn-xs" (click)="submitGlobConf('retry')">APPLY</button> </td> </tr> + <tr [ngClass]="{'info': (localSTStatus$ | async)?.connected, 'danger': !(localSTStatus$ | async)?.connected}"> + <th><label>Local Sync-tool URL</label></th> + <td> <input type="text" [(ngModel)]="syncToolUrl"></td> + <td> + <button class="btn btn-link" (click)="xdsAgentRestartConn()"><span class="fa fa-refresh fa-size-x2"></span></button> + </td> + </tr> <tr class="info"> <th><label>Local Projects root directory</label></th> <td> <input type="text" [(ngModel)]="projectsRootDir" (ngModelChange)="showApplyBtn['rootDir'] = true"></td> diff --git a/webapp/src/app/config/config.component.ts b/webapp/src/app/config/config.component.ts index 745e9f6..1e1e9c2 100644 --- a/webapp/src/app/config/config.component.ts +++ b/webapp/src/app/config/config.component.ts @@ -8,7 +8,8 @@ import 'rxjs/add/operator/filter'; import 'rxjs/add/operator/debounceTime'; import { ConfigService, IConfig, IProject, ProjectType } from "../common/config.service"; -import { XDSServerService, IServerStatus } from "../common/xdsserver.service"; +import { XDSServerService, IServerStatus, IXDSAgentInfo } from "../common/xdsserver.service"; +import { XDSAgentService, IAgentStatus } from "../common/xdsagent.service"; import { SyncthingService, ISyncThingStatus } from "../common/syncthing.service"; import { AlertService } from "../common/alert.service"; import { ISdk, SdkService } from "../common/sdk.service"; @@ -25,15 +26,18 @@ export class ConfigComponent implements OnInit { config$: Observable<IConfig>; sdks$: Observable<ISdk[]>; - severStatus$: Observable<IServerStatus>; + serverStatus$: Observable<IServerStatus>; + agentStatus$: Observable<IAgentStatus>; localSTStatus$: Observable<ISyncThingStatus>; curProj: number; userEditedLabel: boolean = false; + xdsAgentZipUrl: string = ""; // TODO replace by reactive FormControl + add validation syncToolUrl: string; - syncToolRetry: string; + xdsAgentUrl: string; + xdsAgentRetry: string; projectsRootDir: string; showApplyBtn = { // Used to show/hide Apply buttons "retry": false, @@ -46,7 +50,8 @@ export class ConfigComponent implements OnInit { constructor( private configSvr: ConfigService, - private xdsSvr: XDSServerService, + private xdsServerSvr: XDSServerService, + private xdsAgentSvr: XDSAgentService, private stSvr: SyncthingService, private sdkSvr: SdkService, private alert: AlertService, @@ -63,14 +68,17 @@ export class ConfigComponent implements OnInit { ngOnInit() { this.config$ = this.configSvr.conf; this.sdks$ = this.sdkSvr.Sdks$; - this.severStatus$ = this.xdsSvr.Status$; + this.serverStatus$ = this.xdsServerSvr.Status$; + this.agentStatus$ = this.xdsAgentSvr.Status$; this.localSTStatus$ = this.stSvr.Status$; - // Bind syncToolUrl to baseURL + // Bind xdsAgentUrl to baseURL this.config$.subscribe(cfg => { this.syncToolUrl = cfg.localSThg.URL; - this.syncToolRetry = String(cfg.localSThg.retry); + this.xdsAgentUrl = cfg.xdsAgent.URL; + this.xdsAgentRetry = String(cfg.xdsAgent.retry); this.projectsRootDir = cfg.projectsRootDir; + this.xdsAgentZipUrl = cfg.xdsAgentZipUrl; }); // Auto create label name @@ -93,9 +101,9 @@ export class ConfigComponent implements OnInit { switch (field) { case "retry": let re = new RegExp('^[0-9]+$'); - let rr = parseInt(this.syncToolRetry, 10); - if (re.test(this.syncToolRetry) && rr >= 0) { - this.configSvr.syncToolRetry = rr; + let rr = parseInt(this.xdsAgentRetry, 10); + if (re.test(this.xdsAgentRetry) && rr >= 0) { + this.configSvr.xdsAgentRetry = rr; } else { this.alert.warning("Not a valid number", true); } @@ -109,8 +117,10 @@ export class ConfigComponent implements OnInit { this.showApplyBtn[field] = false; } - syncToolRestartConn() { + xdsAgentRestartConn() { + let aurl = this.xdsAgentUrl; this.configSvr.syncToolURL = this.syncToolUrl; + this.configSvr.xdsAgentUrl = aurl; this.configSvr.loadProjects(); } |