aboutsummaryrefslogtreecommitdiffstats
path: root/webapp/src/app/@core-xds
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/src/app/@core-xds')
-rw-r--r--webapp/src/app/@core-xds/core-xds.module.ts54
-rw-r--r--webapp/src/app/@core-xds/module-import-guard.ts5
-rw-r--r--webapp/src/app/@core-xds/services/@core-xds-services.module.ts39
-rw-r--r--webapp/src/app/@core-xds/services/alert.service.spec.ts15
-rw-r--r--webapp/src/app/@core-xds/services/alert.service.ts73
-rw-r--r--webapp/src/app/@core-xds/services/config.service.spec.ts15
-rw-r--r--webapp/src/app/@core-xds/services/config.service.ts61
-rw-r--r--webapp/src/app/@core-xds/services/project.service.spec.ts17
-rw-r--r--webapp/src/app/@core-xds/services/project.service.ts207
-rw-r--r--webapp/src/app/@core-xds/services/sdk.service.ts66
-rw-r--r--webapp/src/app/@core-xds/services/users.service.ts32
-rw-r--r--webapp/src/app/@core-xds/services/xds-config.service.ts97
-rw-r--r--webapp/src/app/@core-xds/services/xdsagent.service.ts397
13 files changed, 1078 insertions, 0 deletions
diff --git a/webapp/src/app/@core-xds/core-xds.module.ts b/webapp/src/app/@core-xds/core-xds.module.ts
new file mode 100644
index 0000000..c5babc3
--- /dev/null
+++ b/webapp/src/app/@core-xds/core-xds.module.ts
@@ -0,0 +1,54 @@
+import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+import { NbAuthModule, NbDummyAuthProvider } from '@nebular/auth';
+import { CookieModule } from 'ngx-cookie';
+
+import { throwIfAlreadyLoaded } from './module-import-guard';
+import { XdsServicesModule } from './services/@core-xds-services.module';
+import { AnalyticsService } from '../@core/utils/analytics.service';
+import { StateService } from '../@core/data/state.service';
+
+const NB_COREXDS_PROVIDERS = [
+ ...XdsServicesModule.forRoot().providers,
+ ...NbAuthModule.forRoot({
+ providers: {
+ email: {
+ service: NbDummyAuthProvider,
+ config: {
+ delay: 3000,
+ login: {
+ rememberMe: true,
+ },
+ },
+ },
+ },
+ }).providers,
+ AnalyticsService,
+ StateService,
+];
+
+@NgModule({
+ imports: [
+ CommonModule,
+ CookieModule.forRoot(),
+ ],
+ exports: [
+ NbAuthModule,
+ ],
+ declarations: [],
+})
+export class CoreXdsModule {
+ constructor( @Optional() @SkipSelf() parentModule: CoreXdsModule) {
+ throwIfAlreadyLoaded(parentModule, 'CoreXdsModule');
+ }
+
+ static forRoot(): ModuleWithProviders {
+ return <ModuleWithProviders>{
+ ngModule: CoreXdsModule,
+ providers: [
+ ...NB_COREXDS_PROVIDERS,
+ ],
+ };
+ }
+}
diff --git a/webapp/src/app/@core-xds/module-import-guard.ts b/webapp/src/app/@core-xds/module-import-guard.ts
new file mode 100644
index 0000000..445640c
--- /dev/null
+++ b/webapp/src/app/@core-xds/module-import-guard.ts
@@ -0,0 +1,5 @@
+export function throwIfAlreadyLoaded(parentModule: any, moduleName: string) {
+ if (parentModule) {
+ throw new Error(`${moduleName} has already been loaded. Import Core modules in the AppModule only.`);
+ }
+}
diff --git a/webapp/src/app/@core-xds/services/@core-xds-services.module.ts b/webapp/src/app/@core-xds/services/@core-xds-services.module.ts
new file mode 100644
index 0000000..13714e1
--- /dev/null
+++ b/webapp/src/app/@core-xds/services/@core-xds-services.module.ts
@@ -0,0 +1,39 @@
+import { NgModule, ModuleWithProviders } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+import { AlertService } from './alert.service';
+import { ConfigService } from './config.service';
+import { ProjectService } from './project.service';
+import { SdkService } from './sdk.service';
+import { UserService } from './users.service';
+import { XDSConfigService } from './xds-config.service';
+import { XDSAgentService } from './xdsagent.service';
+
+const SERVICES = [
+ AlertService,
+ ConfigService,
+ ProjectService,
+ SdkService,
+ UserService,
+ XDSConfigService,
+ XDSAgentService,
+];
+
+@NgModule({
+ imports: [
+ CommonModule,
+ ],
+ providers: [
+ ...SERVICES,
+ ],
+})
+export class XdsServicesModule {
+ static forRoot(): ModuleWithProviders {
+ return <ModuleWithProviders>{
+ ngModule: XdsServicesModule,
+ providers: [
+ ...SERVICES,
+ ],
+ };
+ }
+}
diff --git a/webapp/src/app/@core-xds/services/alert.service.spec.ts b/webapp/src/app/@core-xds/services/alert.service.spec.ts
new file mode 100644
index 0000000..b3d364c
--- /dev/null
+++ b/webapp/src/app/@core-xds/services/alert.service.spec.ts
@@ -0,0 +1,15 @@
+import { TestBed, inject } from '@angular/core/testing';
+
+import { AlertService } from './alert.service';
+
+describe('AlertService', () => {
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [AlertService]
+ });
+ });
+
+ it('should be created', inject([AlertService], (service: AlertService) => {
+ expect(service).toBeTruthy();
+ }));
+});
diff --git a/webapp/src/app/@core-xds/services/alert.service.ts b/webapp/src/app/@core-xds/services/alert.service.ts
new file mode 100644
index 0000000..c15e176
--- /dev/null
+++ b/webapp/src/app/@core-xds/services/alert.service.ts
@@ -0,0 +1,73 @@
+import { Injectable, SecurityContext } from '@angular/core';
+import { Observable } from 'rxjs/Observable';
+import { Subject } from 'rxjs/Subject';
+
+
+export type AlertType = 'error' | 'warning' | 'info' | 'success';
+
+export interface IAlert {
+ type: AlertType;
+ msg: string;
+ show?: boolean;
+ dismissible?: boolean;
+ dismissTimeout?: number; // close alert after this time (in seconds)
+ id?: number;
+}
+
+@Injectable()
+export class AlertService {
+ public alerts: Observable<IAlert[]>;
+
+ private _alerts: IAlert[];
+ private alertsSubject = <Subject<IAlert[]>>new Subject();
+ private uid = 0;
+ private defaultDismissTmo = 5; // in seconds
+
+ constructor() {
+ this.alerts = this.alertsSubject.asObservable();
+ this._alerts = [];
+ this.uid = 0;
+ }
+
+ public error(msg: string, dismissTime?: number) {
+ this.add({
+ type: 'error', msg: msg, dismissible: true, dismissTimeout: dismissTime
+ });
+ }
+
+ public warning(msg: string, dismissible?: boolean) {
+ this.add({ type: 'warning', msg: msg, dismissible: true, dismissTimeout: (dismissible ? this.defaultDismissTmo : 0) });
+ }
+
+ public info(msg: string) {
+ this.add({ type: 'info', msg: msg, dismissible: true, dismissTimeout: this.defaultDismissTmo });
+ }
+
+ public add(al: IAlert) {
+ const msg = String(al.msg).replace('\n', '<br>');
+ // this._alerts.push({
+ this._alerts = [{
+ show: true,
+ type: al.type,
+ msg: msg,
+ dismissible: al.dismissible || true,
+ dismissTimeout: (al.dismissTimeout * 1000) || 0,
+ id: this.uid,
+ }];
+ this.uid += 1;
+ this.alertsSubject.next(this._alerts);
+
+ }
+
+ public del(al: IAlert) {
+ /*
+ const idx = this._alerts.findIndex((a) => a.id === al.id);
+ if (idx > -1) {
+ this._alerts.splice(idx, 1);
+ this.alertsSubject.next(this._alerts);
+ }
+ */
+ this._alerts = [];
+ this.alertsSubject.next(this._alerts);
+ }
+}
diff --git a/webapp/src/app/@core-xds/services/config.service.spec.ts b/webapp/src/app/@core-xds/services/config.service.spec.ts
new file mode 100644
index 0000000..a20d4ba
--- /dev/null
+++ b/webapp/src/app/@core-xds/services/config.service.spec.ts
@@ -0,0 +1,15 @@
+import { TestBed, inject } from '@angular/core/testing';
+
+import { ConfigService } from './config.service';
+
+describe('ConfigService', () => {
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [ConfigService]
+ });
+ });
+
+ it('should be created', inject([ConfigService], (service: ConfigService) => {
+ expect(service).toBeTruthy();
+ }));
+});
diff --git a/webapp/src/app/@core-xds/services/config.service.ts b/webapp/src/app/@core-xds/services/config.service.ts
new file mode 100644
index 0000000..1ba9f2d
--- /dev/null
+++ b/webapp/src/app/@core-xds/services/config.service.ts
@@ -0,0 +1,61 @@
+import { Injectable } from '@angular/core';
+import { CookieService } from 'ngx-cookie';
+import { Observable } from 'rxjs/Observable';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+
+import { AlertService, IAlert } from '../services/alert.service';
+
+export interface IConfig {
+ language: string;
+ projectsRootDir: string;
+}
+
+@Injectable()
+export class ConfigService {
+
+ public Conf$: Observable<IConfig>;
+
+ private confSubject: BehaviorSubject<IConfig>;
+ private confStore: IConfig;
+
+ constructor(
+ private cookie: CookieService,
+ private alert: AlertService,
+ ) {
+ this.load();
+ this.confSubject = <BehaviorSubject<IConfig>>new BehaviorSubject(this.confStore);
+ this.Conf$ = this.confSubject.asObservable();
+ }
+
+ // Load config
+ load() {
+ // Try to retrieve previous config from cookie
+ const cookConf = this.cookie.getObject('xds-config');
+ if (cookConf != null) {
+ this.confStore = <IConfig>cookConf;
+ } else {
+ // Set default config
+ this.confStore = {
+ language: 'ENG',
+ projectsRootDir: '',
+ // projects: []
+ };
+ }
+ }
+
+ // Save config into cookie
+ save() {
+ // Notify subscribers
+ this.confSubject.next(Object.assign({}, this.confStore));
+
+ // Don't save projects in cookies (too big!)
+ const cfg = Object.assign({}, this.confStore);
+ this.cookie.putObject('xds-config', cfg);
+ }
+
+ set projectsRootDir(p: string) {
+ this.confStore.projectsRootDir = p;
+ this.save();
+ }
+
+}
diff --git a/webapp/src/app/@core-xds/services/project.service.spec.ts b/webapp/src/app/@core-xds/services/project.service.spec.ts
new file mode 100644
index 0000000..b8edfc7
--- /dev/null
+++ b/webapp/src/app/@core-xds/services/project.service.spec.ts
@@ -0,0 +1,17 @@
+import { TestBed, inject } from '@angular/core/testing';
+
+import { ProjectService } from './project.service';
+
+describe('ProjectService', () => {
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [ProjectService]
+ });
+ });
+
+ it('should be created', inject([ProjectService], (service: ProjectService) => {
+ expect(service).toBeTruthy();
+ }));
+
+ // FIXME SEB - add more tests, see https://angular.io/guide/http#mocking-philosophy
+});
diff --git a/webapp/src/app/@core-xds/services/project.service.ts b/webapp/src/app/@core-xds/services/project.service.ts
new file mode 100644
index 0000000..8aeed80
--- /dev/null
+++ b/webapp/src/app/@core-xds/services/project.service.ts
@@ -0,0 +1,207 @@
+import { Injectable, SecurityContext } from '@angular/core';
+import { Observable } from 'rxjs/Observable';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+
+import { XDSAgentService, IXDSProjectConfig } from '../services/xdsagent.service';
+
+/* FIXME: syntax only compatible with TS>2.4.0
+export enum ProjectType {
+ UNSET = '',
+ NATIVE_PATHMAP = 'PathMap',
+ SYNCTHING = 'CloudSync'
+}
+*/
+export type ProjectTypeEnum = '' | 'PathMap' | 'CloudSync';
+export const ProjectType = {
+ UNSET: '',
+ NATIVE_PATHMAP: 'PathMap',
+ SYNCTHING: 'CloudSync'
+};
+
+export const ProjectTypes = [
+ { value: ProjectType.NATIVE_PATHMAP, display: 'Path mapping' },
+ { value: ProjectType.SYNCTHING, display: 'Cloud Sync' }
+];
+
+export const ProjectStatus = {
+ ErrorConfig: 'ErrorConfig',
+ Disable: 'Disable',
+ Enable: 'Enable',
+ Pause: 'Pause',
+ Syncing: 'Syncing'
+};
+
+export interface IProject {
+ id?: string;
+ serverId: string;
+ label: string;
+ pathClient: string;
+ pathServer?: string;
+ type: ProjectTypeEnum;
+ status?: string;
+ isInSync?: boolean;
+ isUsable?: boolean;
+ serverPrjDef?: IXDSProjectConfig;
+ isExpanded?: boolean;
+ visible?: boolean;
+ defaultSdkID?: string;
+}
+
+@Injectable()
+export class ProjectService {
+ public Projects$: Observable<IProject[]>;
+
+ private _prjsList: IProject[] = [];
+ private current: IProject;
+ private prjsSubject = <BehaviorSubject<IProject[]>>new BehaviorSubject(this._prjsList);
+
+ constructor(private xdsSvr: XDSAgentService) {
+ this.current = null;
+ this.Projects$ = this.prjsSubject.asObservable();
+
+ this.xdsSvr.getProjects().subscribe((projects) => {
+ this._prjsList = [];
+ projects.forEach(p => {
+ this._addProject(p, true);
+ });
+ this.prjsSubject.next(Object.assign([], this._prjsList));
+ });
+
+ // Update Project data
+ this.xdsSvr.ProjectState$.subscribe(prj => {
+ const i = this._getProjectIdx(prj.id);
+ if (i >= 0) {
+ // XXX for now, only isInSync and status may change
+ this._prjsList[i].isInSync = prj.isInSync;
+ this._prjsList[i].status = prj.status;
+ this._prjsList[i].isUsable = this._isUsableProject(prj);
+ this.prjsSubject.next(Object.assign([], this._prjsList));
+ }
+ });
+
+ // Add listener on create and delete project events
+ this.xdsSvr.addEventListener('event:project-add', (ev) => {
+ if (ev && ev.data && ev.data.id) {
+ this._addProject(ev.data);
+ } else {
+ console.log('Warning: received events with unknown data: ev=', ev);
+ }
+ });
+ this.xdsSvr.addEventListener('event:project-delete', (ev) => {
+ if (ev && ev.data && ev.data.id) {
+ const idx = this._prjsList.findIndex(item => item.id === ev.data.id);
+ if (idx === -1) {
+ console.log('Warning: received events on unknown project id: ev=', ev);
+ return;
+ }
+ this._prjsList.splice(idx, 1);
+ this.prjsSubject.next(Object.assign([], this._prjsList));
+ } else {
+ console.log('Warning: received events with unknown data: ev=', ev);
+ }
+ });
+
+ }
+
+ public setCurrent(s: IProject) {
+ this.current = s;
+ }
+
+ public getCurrent(): IProject {
+ return this.current;
+ }
+
+ public getCurrentId(): string {
+ if (this.current && this.current.id) {
+ return this.current.id;
+ }
+ return '';
+ }
+
+ Add(prj: IProject): Observable<IProject> {
+ const xdsPrj: IXDSProjectConfig = {
+ id: '',
+ serverId: prj.serverId,
+ label: prj.label || '',
+ clientPath: prj.pathClient.trim(),
+ serverPath: prj.pathServer,
+ type: prj.type,
+ defaultSdkID: prj.defaultSdkID,
+ };
+ // Send config to XDS server
+ return this.xdsSvr.addProject(xdsPrj)
+ .map(xp => this._convToIProject(xp));
+ }
+
+ Delete(prj: IProject): Observable<IProject> {
+ const idx = this._getProjectIdx(prj.id);
+ const delPrj = prj;
+ if (idx === -1) {
+ throw new Error('Invalid project id (id=' + prj.id + ')');
+ }
+ return this.xdsSvr.deleteProject(prj.id)
+ .map(res => delPrj);
+ }
+
+ Sync(prj: IProject): Observable<string> {
+ const idx = this._getProjectIdx(prj.id);
+ if (idx === -1) {
+ throw new Error('Invalid project id (id=' + prj.id + ')');
+ }
+ return this.xdsSvr.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._prjsList.findIndex((item) => item.id === id);
+ }
+
+ private _convToIProject(rPrj: IXDSProjectConfig): IProject {
+ // Convert XDSFolderConfig to IProject
+ const pp: IProject = {
+ id: rPrj.id,
+ serverId: rPrj.serverId,
+ label: rPrj.label,
+ pathClient: rPrj.clientPath,
+ pathServer: rPrj.serverPath,
+ type: rPrj.type,
+ status: rPrj.status,
+ isInSync: rPrj.isInSync,
+ isUsable: this._isUsableProject(rPrj),
+ defaultSdkID: rPrj.defaultSdkID,
+ serverPrjDef: Object.assign({}, rPrj), // do a copy
+ };
+ return pp;
+ }
+
+ private _addProject(rPrj: IXDSProjectConfig, noNext?: boolean): IProject {
+
+ // Convert XDSFolderConfig to IProject
+ const pp = this._convToIProject(rPrj);
+
+ // add new project
+ this._prjsList.push(pp);
+
+ // sort project array
+ this._prjsList.sort((a, b) => {
+ if (a.label < b.label) {
+ return -1;
+ }
+ if (a.label > b.label) {
+ return 1;
+ }
+ return 0;
+ });
+
+ if (!noNext) {
+ this.prjsSubject.next(Object.assign([], this._prjsList));
+ }
+
+ return pp;
+ }
+}
diff --git a/webapp/src/app/@core-xds/services/sdk.service.ts b/webapp/src/app/@core-xds/services/sdk.service.ts
new file mode 100644
index 0000000..d1daa86
--- /dev/null
+++ b/webapp/src/app/@core-xds/services/sdk.service.ts
@@ -0,0 +1,66 @@
+import { Injectable, SecurityContext } from '@angular/core';
+import { Observable } from 'rxjs/Observable';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+
+import { XDSAgentService } from '../services/xdsagent.service';
+
+import 'rxjs/add/observable/throw';
+
+export interface ISdk {
+ id: string;
+ profile: string;
+ version: string;
+ arch: number;
+ path: string;
+}
+
+@Injectable()
+export class SdkService {
+ public Sdks$: Observable<ISdk[]>;
+
+ private _sdksList = [];
+ private current: ISdk;
+ private sdksSubject = <BehaviorSubject<ISdk[]>>new BehaviorSubject(this._sdksList);
+
+ constructor(private xdsSvr: XDSAgentService) {
+ this.current = null;
+ this.Sdks$ = this.sdksSubject.asObservable();
+
+ this.xdsSvr.XdsConfig$.subscribe(cfg => {
+ if (!cfg || cfg.servers.length < 1) {
+ return;
+ }
+ // FIXME support multiple server
+ // cfg.servers.forEach(svr => {
+ this.xdsSvr.getSdks(cfg.servers[0].id).subscribe((s) => {
+ this._sdksList = s;
+ this.sdksSubject.next(s);
+ });
+ });
+ }
+
+ public setCurrent(s: ISdk) {
+ this.current = s;
+ }
+
+ public getCurrent(): ISdk {
+ return this.current;
+ }
+
+ public getCurrentId(): string {
+ if (this.current && this.current.id) {
+ return this.current.id;
+ }
+ return '';
+ }
+
+ public add(sdk: ISdk): Observable<ISdk> {
+ // TODO SEB
+ return Observable.throw('Not implement yet');
+ }
+
+ public delete(sdk: ISdk): Observable<ISdk> {
+ // TODO SEB
+ return Observable.throw('Not implement yet');
+ }
+}
diff --git a/webapp/src/app/@core-xds/services/users.service.ts b/webapp/src/app/@core-xds/services/users.service.ts
new file mode 100644
index 0000000..e187c10
--- /dev/null
+++ b/webapp/src/app/@core-xds/services/users.service.ts
@@ -0,0 +1,32 @@
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs/Observable';
+import 'rxjs/add/observable/of';
+
+let counter = 0;
+
+@Injectable()
+export class UserService {
+
+ private users = {
+ anonymous: { name: 'Anonymous', picture: 'assets/images/anonymous.png' },
+ };
+
+ private userArray: any[];
+
+ constructor() {
+ // this.userArray = Object.values(this.users);
+ }
+
+ getUsers(): Observable<any> {
+ return Observable.of(this.users);
+ }
+
+ getUserArray(): Observable<any[]> {
+ return Observable.of(this.userArray);
+ }
+
+ getUser(): Observable<any> {
+ counter = (counter + 1) % this.userArray.length;
+ return Observable.of(this.userArray[counter]);
+ }
+}
diff --git a/webapp/src/app/@core-xds/services/xds-config.service.ts b/webapp/src/app/@core-xds/services/xds-config.service.ts
new file mode 100644
index 0000000..7559673
--- /dev/null
+++ b/webapp/src/app/@core-xds/services/xds-config.service.ts
@@ -0,0 +1,97 @@
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs/Observable';
+import { Subject } from 'rxjs/Subject';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+
+import { AlertService, IAlert } from '../services/alert.service';
+import { XDSAgentService, IAgentStatus, IXDServerCfg } from '../../@core-xds/services/xdsagent.service';
+
+import 'rxjs/add/operator/publish';
+import 'rxjs/add/operator/map';
+import 'rxjs/add/operator/catch';
+
+
+@Injectable()
+export class XDSConfigService {
+
+ // Conf$: Observable<IXdsConfig>;
+ xdsServers: IXDServerCfg[];
+
+ // private confSubject: BehaviorSubject<IXdsConfig>;
+ // private confStore: IXdsConfig;
+
+ private _curServer: IXDServerCfg = { id: '', url: '', connRetry: 0, connected: false };
+ private curServer$ = new Subject<IXDServerCfg>();
+
+ constructor(
+ private alert: AlertService,
+ private xdsAgentSvr: XDSAgentService,
+ ) {
+ /*
+ this.confSubject = <BehaviorSubject<IXdsConfig>>new BehaviorSubject(this.confStore);
+ this.Conf$ = this.confSubject.asObservable();
+ */
+
+ // Update servers list
+ this.xdsAgentSvr.XdsConfig$.subscribe(cfg => {
+ if (!cfg || cfg.servers.length < 1) {
+ return;
+ }
+ this.xdsServers = cfg.servers;
+ this._updateCurServer();
+ });
+ }
+
+ onCurServer(): Observable<IXDServerCfg> {
+ return this.curServer$.publish().refCount();
+ }
+
+ getCurServer(): IXDServerCfg {
+ return Object.assign({}, this._curServer);
+ }
+
+ setCurServer(svr: IXDServerCfg): Observable<IXDServerCfg> {
+ const curSvr = this._getCurServer();
+
+ if (!curSvr.connected || curSvr.url !== svr.url) {
+ return this.xdsAgentSvr.setServerUrl(curSvr.id, svr.url, svr.connRetry)
+ .map(cfg => this._updateCurServer())
+ .catch(err => {
+ this._curServer.connected = false;
+ this.curServer$.next(Object.assign({}, this._curServer));
+ return Observable.throw(err);
+ });
+ } else {
+ if (curSvr.connRetry !== svr.connRetry) {
+ return this.xdsAgentSvr.setServerRetry(curSvr.id, svr.connRetry)
+ .map(cfg => this._updateCurServer())
+ .catch(err => {
+ this.curServer$.next(Object.assign({}, this._curServer));
+ return Observable.throw(err);
+ });
+ }
+ }
+ return Observable.of(curSvr);
+ }
+
+ private _updateCurServer() {
+ this._curServer = this._getCurServer();
+ this.curServer$.next(Object.assign({}, this._curServer));
+ }
+
+ private _getCurServer(url?: string): IXDServerCfg {
+ if (!this.xdsServers) {
+ return this._curServer;
+ }
+
+ // Init the 1st time
+ let svrUrl = url || this._curServer.url;
+ if (this._curServer.url === '' && this.xdsServers.length > 0) {
+ svrUrl = this.xdsServers[0].url;
+ }
+
+ const svr = this.xdsServers.filter(s => s.url === svrUrl);
+ return svr[0];
+ }
+
+}
diff --git a/webapp/src/app/@core-xds/services/xdsagent.service.ts b/webapp/src/app/@core-xds/services/xdsagent.service.ts
new file mode 100644
index 0000000..56e493f
--- /dev/null
+++ b/webapp/src/app/@core-xds/services/xdsagent.service.ts
@@ -0,0 +1,397 @@
+import { Injectable, Inject } from '@angular/core';
+import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
+import { DOCUMENT } 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 { ISdk } from './sdk.service';
+import { ProjectType, ProjectTypeEnum } from './project.service';
+
+// Import RxJs required methods
+import 'rxjs/add/operator/map';
+import 'rxjs/add/operator/catch';
+import 'rxjs/add/observable/throw';
+import 'rxjs/add/operator/mergeMap';
+import 'rxjs/add/observable/of';
+import 'rxjs/add/operator/retryWhen';
+
+
+export interface IXDSConfigProject {
+ id: string;
+ path: string;
+ clientSyncThingID: string;
+ type: string;
+ label?: string;
+ defaultSdkID?: string;
+}
+
+interface IXDSBuilderConfig {
+ ip: string;
+ port: string;
+ syncThingID: string;
+}
+
+export interface IXDSProjectConfig {
+ id: string;
+ serverId: string;
+ label: string;
+ clientPath: string;
+ serverPath?: string;
+ type: ProjectTypeEnum;
+ status?: string;
+ isInSync?: boolean;
+ defaultSdkID: string;
+}
+
+export interface IXDSVer {
+ id: string;
+ version: string;
+ apiVersion: string;
+ gitTag: string;
+}
+
+export interface IXDSVersions {
+ client: IXDSVer;
+ servers: IXDSVer[];
+}
+
+export interface IXDServerCfg {
+ id: string;
+ url: string;
+ apiUrl?: string;
+ partialUrl?: string;
+ connRetry: number;
+ connected: boolean;
+}
+
+export interface IXDSConfig {
+ servers: IXDServerCfg[];
+}
+
+export interface ISdkMessage {
+ wsID: string;
+ msgType: string;
+ data: any;
+}
+
+export interface ICmdOutput {
+ cmdID: string;
+ timestamp: string;
+ stdout: string;
+ stderr: string;
+}
+
+export interface ICmdExit {
+ cmdID: string;
+ timestamp: string;
+ code: number;
+ error: string;
+}
+
+export interface IServerStatus {
+ id: string;
+ connected: boolean;
+}
+
+export interface IAgentStatus {
+ connected: boolean;
+ servers: IServerStatus[];
+}
+
+
+@Injectable()
+export class XDSAgentService {
+
+ public XdsConfig$: Observable<IXDSConfig>;
+ public Status$: Observable<IAgentStatus>;
+ public ProjectState$ = <Subject<IXDSProjectConfig>>new Subject();
+ public CmdOutput$ = <Subject<ICmdOutput>>new Subject();
+ public CmdExit$ = <Subject<ICmdExit>>new Subject();
+
+ private baseUrl: string;
+ private wsUrl: string;
+ private _config = <IXDSConfig>{ servers: [] };
+ private _status = { connected: false, servers: [] };
+
+ private configSubject = <BehaviorSubject<IXDSConfig>>new BehaviorSubject(this._config);
+ private statusSubject = <BehaviorSubject<IAgentStatus>>new BehaviorSubject(this._status);
+
+ private socket: SocketIOClient.Socket;
+
+ constructor( @Inject(DOCUMENT) private document: Document,
+ private http: HttpClient, private alert: AlertService) {
+
+ this.XdsConfig$ = this.configSubject.asObservable();
+ this.Status$ = this.statusSubject.asObservable();
+
+ const originUrl = this.document.location.origin;
+ this.baseUrl = originUrl + '/api/v1';
+
+ const re = originUrl.match(/http[s]?:\/\/([^\/]*)[\/]?/);
+ if (re === null || re.length < 2) {
+ console.error('ERROR: cannot determine Websocket url');
+ } else {
+ this.wsUrl = 'ws://' + re[1];
+ this._handleIoSocket();
+ this._RegisterEvents();
+ }
+ }
+
+ private _NotifyXdsAgentState(sts: boolean) {
+ this._status.connected = sts;
+ this.statusSubject.next(Object.assign({}, this._status));
+
+ // Update XDS config including XDS Server list when connected
+ if (sts) {
+ this.getConfig().subscribe(c => {
+ this._config = c;
+ this._NotifyXdsServerState();
+ this.configSubject.next(Object.assign({ servers: [] }, this._config));
+ });
+ }
+ }
+
+ private _NotifyXdsServerState() {
+ this._status.servers = this._config.servers.map(svr => {
+ return { id: svr.id, connected: svr.connected };
+ });
+ this.statusSubject.next(Object.assign({}, this._status));
+ }
+
+ private _handleIoSocket() {
+ this.socket = io(this.wsUrl, { transports: ['websocket'] });
+
+ this.socket.on('connect_error', (res) => {
+ this._NotifyXdsAgentState(false);
+ console.error('XDS Agent WebSocket Connection error !');
+ });
+
+ this.socket.on('connect', (res) => {
+ this._NotifyXdsAgentState(true);
+ });
+
+ this.socket.on('disconnection', (res) => {
+ this._NotifyXdsAgentState(false);
+ this.alert.error('WS disconnection: ' + res);
+ });
+
+ this.socket.on('error', (err) => {
+ console.error('WS error:', err);
+ });
+
+ this.socket.on('make:output', data => {
+ this.CmdOutput$.next(Object.assign({}, <ICmdOutput>data));
+ });
+
+ this.socket.on('make:exit', data => {
+ this.CmdExit$.next(Object.assign({}, <ICmdExit>data));
+ });
+
+ this.socket.on('exec:output', data => {
+ this.CmdOutput$.next(Object.assign({}, <ICmdOutput>data));
+ });
+
+ this.socket.on('exec:exit', data => {
+ this.CmdExit$.next(Object.assign({}, <ICmdExit>data));
+ });
+
+ // Events
+ // (project-add and project-delete events are managed by project.service)
+ this.socket.on('event:server-config', ev => {
+ if (ev && ev.data) {
+ const cfg: IXDServerCfg = ev.data;
+ const idx = this._config.servers.findIndex(el => el.id === cfg.id);
+ if (idx >= 0) {
+ this._config.servers[idx] = Object.assign({}, cfg);
+ this._NotifyXdsServerState();
+ }
+ this.configSubject.next(Object.assign({}, this._config));
+ }
+ });
+
+ this.socket.on('event:project-state-change', ev => {
+ if (ev && ev.data) {
+ this.ProjectState$.next(Object.assign({}, ev.data));
+ }
+ });
+
+ }
+
+ /**
+ ** Events
+ ***/
+ addEventListener(ev: string, fn: Function): SocketIOClient.Emitter {
+ return this.socket.addEventListener(ev, fn);
+ }
+
+ /**
+ ** Misc / Version
+ ***/
+ getVersion(): Observable<IXDSVersions> {
+ return this._get('/version');
+ }
+
+ /***
+ ** Config
+ ***/
+ getConfig(): Observable<IXDSConfig> {
+ return this._get('/config');
+ }
+
+ setConfig(cfg: IXDSConfig): Observable<IXDSConfig> {
+ return this._post('/config', cfg);
+ }
+
+ setServerRetry(serverID: string, retry: number): Observable<IXDSConfig> {
+ const svr = this._getServer(serverID);
+ if (!svr) {
+ return Observable.throw('Unknown server ID');
+ }
+ if (retry < 0 || Number.isNaN(retry) || retry == null) {
+ return Observable.throw('Not a valid number');
+ }
+ svr.connRetry = retry;
+ return this._setConfig();
+ }
+
+ setServerUrl(serverID: string, url: string, retry: number): Observable<IXDSConfig> {
+ const svr = this._getServer(serverID);
+ if (!svr) {
+ return Observable.throw('Unknown server ID');
+ }
+ svr.connected = false;
+ svr.url = url;
+ if (!Number.isNaN(retry) && retry > 0) {
+ svr.connRetry = retry;
+ }
+ this._NotifyXdsServerState();
+ return this._setConfig();
+ }
+
+ private _setConfig(): Observable<IXDSConfig> {
+ return this.setConfig(this._config)
+ .map(newCfg => {
+ this._config = newCfg;
+ this.configSubject.next(Object.assign({}, this._config));
+ return this._config;
+ });
+ }
+
+ /***
+ ** SDKs
+ ***/
+ getSdks(serverID: string): Observable<ISdk[]> {
+ const svr = this._getServer(serverID);
+ if (!svr || !svr.connected) {
+ return Observable.of([]);
+ }
+
+ return this._get(svr.partialUrl + '/sdks');
+ }
+
+ /***
+ ** Projects
+ ***/
+ getProjects(): Observable<IXDSProjectConfig[]> {
+ return this._get('/projects');
+ }
+
+ addProject(cfg: IXDSProjectConfig): Observable<IXDSProjectConfig> {
+ return this._post('/projects', cfg);
+ }
+
+ deleteProject(id: string): Observable<IXDSProjectConfig> {
+ return this._delete('/projects/' + id);
+ }
+
+ syncProject(id: string): Observable<string> {
+ return this._post('/projects/sync/' + id, {});
+ }
+
+ /***
+ ** Exec
+ ***/
+ exec(prjID: string, dir: string, cmd: string, sdkid?: string, args?: string[], env?: string[]): Observable<any> {
+ return this._post('/exec',
+ {
+ id: prjID,
+ rpath: dir,
+ cmd: cmd,
+ sdkID: sdkid || '',
+ args: args || [],
+ env: env || [],
+ });
+ }
+
+ /**
+ ** Private functions
+ ***/
+
+ private _RegisterEvents() {
+ // Register to all existing events
+ this._post('/events/register', { 'name': 'event:all' })
+ .subscribe(
+ res => { },
+ error => {
+ this.alert.error('ERROR while registering to all events: ' + error);
+ }
+ );
+ }
+
+ private _getServer(ID: string): IXDServerCfg {
+ const svr = this._config.servers.filter(item => item.id === ID);
+ if (svr.length < 1) {
+ return null;
+ }
+ return svr[0];
+ }
+
+ private _attachAuthHeaders(options?: any) {
+ options = options || {};
+ const headers = options.headers || new HttpHeaders();
+ // headers.append('Authorization', 'Basic ' + btoa('username:password'));
+ headers.append('Accept', 'application/json');
+ headers.append('Content-Type', 'application/json');
+ // headers.append('Access-Control-Allow-Origin', '*');
+
+ options.headers = headers;
+ return options;
+ }
+
+ private _get(url: string): Observable<any> {
+ return this.http.get(this.baseUrl + url, this._attachAuthHeaders())
+ .catch(this._decodeError);
+ }
+ private _post(url: string, body: any): Observable<any> {
+ return this.http.post(this.baseUrl + url, JSON.stringify(body), this._attachAuthHeaders())
+ .catch((error) => {
+ return this._decodeError(error);
+ });
+ }
+ private _delete(url: string): Observable<any> {
+ return this.http.delete(this.baseUrl + url, this._attachAuthHeaders())
+ .catch(this._decodeError);
+ }
+
+ private _decodeError(err: any) {
+ let e: string;
+ if (err instanceof HttpErrorResponse) {
+ e = (err.error && err.error.error) ? err.error.error : err.message || 'Unknown error';
+ } else if (typeof err === 'object') {
+ if (err.statusText) {
+ e = err.statusText;
+ } else if (err.error) {
+ e = String(err.error);
+ } else {
+ e = JSON.stringify(err);
+ }
+ } else {
+ e = err.message ? err.message : err.toString();
+ }
+ console.log('xdsagent.service - ERROR: ', e);
+ return Observable.throw(e);
+ }
+}