aboutsummaryrefslogtreecommitdiffstats
path: root/webapp/src/app
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/src/app')
-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.ts (renamed from webapp/src/app/services/alert.service.spec.ts)0
-rw-r--r--webapp/src/app/@core-xds/services/alert.service.ts73
-rw-r--r--webapp/src/app/@core-xds/services/config.service.spec.ts (renamed from webapp/src/app/services/config.service.spec.ts)0
-rw-r--r--webapp/src/app/@core-xds/services/config.service.ts (renamed from webapp/src/app/services/config.service.ts)3
-rw-r--r--webapp/src/app/@core-xds/services/project.service.spec.ts (renamed from webapp/src/app/services/project.service.spec.ts)0
-rw-r--r--webapp/src/app/@core-xds/services/project.service.ts207
-rw-r--r--webapp/src/app/@core-xds/services/sdk.service.ts (renamed from webapp/src/app/services/sdk.service.ts)12
-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
-rw-r--r--webapp/src/app/@core/data/.gitkeep0
-rw-r--r--webapp/src/app/@core/data/state.service.ts69
-rw-r--r--webapp/src/app/@core/utils/.gitkeep0
-rw-r--r--webapp/src/app/@core/utils/analytics.service.ts31
-rw-r--r--webapp/src/app/@theme/components/footer/footer.component.scss30
-rw-r--r--webapp/src/app/@theme/components/footer/footer.component.ts23
-rw-r--r--webapp/src/app/@theme/components/header/header.component.html32
-rw-r--r--webapp/src/app/@theme/components/header/header.component.scss115
-rw-r--r--webapp/src/app/@theme/components/header/header.component.ts51
-rw-r--r--webapp/src/app/@theme/components/index.ts6
-rw-r--r--webapp/src/app/@theme/components/search-input/search-input.component.scss33
-rw-r--r--webapp/src/app/@theme/components/search-input/search-input.component.ts35
-rw-r--r--webapp/src/app/@theme/components/theme-settings/theme-settings.component.scss36
-rw-r--r--webapp/src/app/@theme/components/theme-settings/theme-settings.component.ts65
-rw-r--r--webapp/src/app/@theme/components/theme-switcher/theme-switcher.component.scss101
-rw-r--r--webapp/src/app/@theme/components/theme-switcher/theme-switcher.component.ts48
-rw-r--r--webapp/src/app/@theme/components/tiny-mce/tiny-mce.component.ts33
-rw-r--r--webapp/src/app/@theme/directives/.gitkeep0
-rw-r--r--webapp/src/app/@theme/layouts/index.ts6
-rw-r--r--webapp/src/app/@theme/layouts/one-column/one-column.layout.scss130
-rw-r--r--webapp/src/app/@theme/layouts/one-column/one-column.layout.ts35
-rw-r--r--webapp/src/app/@theme/layouts/sample/sample.layout.scss130
-rw-r--r--webapp/src/app/@theme/layouts/sample/sample.layout.ts145
-rw-r--r--webapp/src/app/@theme/layouts/three-columns/three-columns.layout.scss130
-rw-r--r--webapp/src/app/@theme/layouts/three-columns/three-columns.layout.ts39
-rw-r--r--webapp/src/app/@theme/layouts/two-columns/two-columns.layout.scss130
-rw-r--r--webapp/src/app/@theme/layouts/two-columns/two-columns.layout.ts37
-rw-r--r--webapp/src/app/@theme/layouts/xds/xds.layout.html46
-rw-r--r--webapp/src/app/@theme/layouts/xds/xds.layout.scss130
-rw-r--r--webapp/src/app/@theme/layouts/xds/xds.layout.ts76
-rw-r--r--webapp/src/app/@theme/pipes/.gitkeep0
-rw-r--r--webapp/src/app/@theme/pipes/capitalize.pipe.ts11
-rw-r--r--webapp/src/app/@theme/pipes/index.ts4
-rw-r--r--webapp/src/app/@theme/pipes/plural.pipe.ts14
-rw-r--r--webapp/src/app/@theme/pipes/round.pipe.ts9
-rw-r--r--webapp/src/app/@theme/pipes/timing.pipe.ts18
-rw-r--r--webapp/src/app/@theme/styles/pace.theme.scss22
-rw-r--r--webapp/src/app/@theme/styles/styles.scss20
-rw-r--r--webapp/src/app/@theme/styles/theme.cosmic.ts87
-rw-r--r--webapp/src/app/@theme/styles/theme.default.ts88
-rw-r--r--webapp/src/app/@theme/styles/themes.scss27
-rw-r--r--webapp/src/app/@theme/theme.module.ts99
-rw-r--r--webapp/src/app/app-alert/app-alert.component.spec.ts25
-rw-r--r--webapp/src/app/app-alert/app-alert.component.ts31
-rw-r--r--webapp/src/app/app-routing.module.ts61
-rw-r--r--webapp/src/app/app-topnav/app-topnav.component.css31
-rw-r--r--webapp/src/app/app-topnav/app-topnav.component.html24
-rw-r--r--webapp/src/app/app-topnav/app-topnav.component.ts13
-rw-r--r--webapp/src/app/app.component.html7
-rw-r--r--webapp/src/app/app.component.ts53
-rw-r--r--webapp/src/app/app.module.ts130
-rw-r--r--webapp/src/app/common/safe.pipe.ts10
-rw-r--r--webapp/src/app/config/config.component.css35
-rw-r--r--webapp/src/app/config/config.component.html101
-rw-r--r--webapp/src/app/config/config.component.spec.ts25
-rw-r--r--webapp/src/app/config/config.component.ts108
-rw-r--r--webapp/src/app/devel/build/build.component.css54
-rw-r--r--webapp/src/app/devel/build/build.component.html114
-rw-r--r--webapp/src/app/devel/build/build.component.ts193
-rw-r--r--webapp/src/app/devel/devel.component.css19
-rw-r--r--webapp/src/app/devel/devel.component.html40
-rw-r--r--webapp/src/app/devel/devel.component.spec.ts25
-rw-r--r--webapp/src/app/devel/devel.component.ts34
-rw-r--r--webapp/src/app/home/home.component.css18
-rw-r--r--webapp/src/app/home/home.component.html13
-rw-r--r--webapp/src/app/home/home.component.ts46
-rw-r--r--webapp/src/app/pages/build/build.component.html63
-rw-r--r--webapp/src/app/pages/build/build.component.scss37
-rw-r--r--webapp/src/app/pages/build/build.component.spec.ts (renamed from webapp/src/app/devel/build/build.component.spec.ts)0
-rw-r--r--webapp/src/app/pages/build/build.component.ts198
-rw-r--r--webapp/src/app/pages/build/build.module.ts20
-rw-r--r--webapp/src/app/pages/build/settings/project-select-dropdown.component.ts40
-rw-r--r--webapp/src/app/pages/build/settings/sdk-select-dropdown.component.ts (renamed from webapp/src/app/sdks/sdkSelectDropdown.component.ts)20
-rw-r--r--webapp/src/app/pages/config/config-global/config-global.component.html37
-rw-r--r--webapp/src/app/pages/config/config-global/config-global.component.scss20
-rw-r--r--webapp/src/app/pages/config/config-global/config-global.component.ts25
-rw-r--r--webapp/src/app/pages/config/config-routing.module.ts32
-rw-r--r--webapp/src/app/pages/config/config-xds/config-xds.component.html34
-rw-r--r--webapp/src/app/pages/config/config-xds/config-xds.component.scss26
-rw-r--r--webapp/src/app/pages/config/config-xds/config-xds.component.ts63
-rw-r--r--webapp/src/app/pages/config/config-xds/downloadXdsAgent.component.ts (renamed from webapp/src/app/config/downloadXdsAgent.component.ts)0
-rw-r--r--webapp/src/app/pages/config/config.component.ts10
-rw-r--r--webapp/src/app/pages/config/config.module.ts15
-rw-r--r--webapp/src/app/pages/dashboard/dashboard.component.html10
-rw-r--r--webapp/src/app/pages/dashboard/dashboard.component.scss16
-rw-r--r--webapp/src/app/pages/dashboard/dashboard.component.ts9
-rw-r--r--webapp/src/app/pages/dashboard/dashboard.module.ts52
-rw-r--r--webapp/src/app/pages/dashboard/status-card/status-card.component.scss142
-rw-r--r--webapp/src/app/pages/dashboard/status-card/status-card.component.ts26
-rw-r--r--webapp/src/app/pages/notifications/notifications.component.scss28
-rw-r--r--webapp/src/app/pages/notifications/notifications.component.ts65
-rw-r--r--webapp/src/app/pages/pages-menu.ts201
-rw-r--r--webapp/src/app/pages/pages-routing.module.ts41
-rw-r--r--webapp/src/app/pages/pages.component.ts18
-rw-r--r--webapp/src/app/pages/pages.module.ts33
-rw-r--r--webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.html51
-rw-r--r--webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.scss1
-rw-r--r--webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.ts143
-rw-r--r--webapp/src/app/pages/projects/project-card/project-card.component.html65
-rw-r--r--webapp/src/app/pages/projects/project-card/project-card.component.scss54
-rw-r--r--webapp/src/app/pages/projects/project-card/project-card.component.ts52
-rw-r--r--webapp/src/app/pages/projects/projects.component.html26
-rw-r--r--webapp/src/app/pages/projects/projects.component.scss83
-rw-r--r--webapp/src/app/pages/projects/projects.component.ts33
-rw-r--r--webapp/src/app/pages/projects/projects.module.ts23
-rw-r--r--webapp/src/app/pages/sdks/sdk-card/sdk-card.component.html48
-rw-r--r--webapp/src/app/pages/sdks/sdk-card/sdk-card.component.scss47
-rw-r--r--webapp/src/app/pages/sdks/sdk-card/sdk-card.component.ts35
-rw-r--r--webapp/src/app/pages/sdks/sdks.component.html26
-rw-r--r--webapp/src/app/pages/sdks/sdks.component.scss83
-rw-r--r--webapp/src/app/pages/sdks/sdks.component.ts35
-rw-r--r--webapp/src/app/pages/sdks/sdks.module.ts22
-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.ts147
-rw-r--r--webapp/src/app/projects/projectCard.component.ts91
-rw-r--r--webapp/src/app/projects/projectsListAccordion.component.ts39
-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/sdks/sdkCard.component.ts55
-rw-r--r--webapp/src/app/sdks/sdksListAccordion.component.ts26
-rw-r--r--webapp/src/app/services/alert.service.ts67
-rw-r--r--webapp/src/app/services/project.service.ts199
-rw-r--r--webapp/src/app/services/utils.service.ts33
-rw-r--r--webapp/src/app/services/xdsagent.service.ts384
138 files changed, 5247 insertions, 2297 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/services/alert.service.spec.ts b/webapp/src/app/@core-xds/services/alert.service.spec.ts
index b3d364c..b3d364c 100644
--- a/webapp/src/app/services/alert.service.spec.ts
+++ b/webapp/src/app/@core-xds/services/alert.service.spec.ts
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/services/config.service.spec.ts b/webapp/src/app/@core-xds/services/config.service.spec.ts
index a20d4ba..a20d4ba 100644
--- a/webapp/src/app/services/config.service.spec.ts
+++ b/webapp/src/app/@core-xds/services/config.service.spec.ts
diff --git a/webapp/src/app/services/config.service.ts b/webapp/src/app/@core-xds/services/config.service.ts
index ffe2b45..1ba9f2d 100644
--- a/webapp/src/app/services/config.service.ts
+++ b/webapp/src/app/@core-xds/services/config.service.ts
@@ -4,7 +4,6 @@ import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { AlertService, IAlert } from '../services/alert.service';
-import { UtilsService } from '../services/utils.service';
export interface IConfig {
language: string;
@@ -22,7 +21,6 @@ export class ConfigService {
constructor(
private cookie: CookieService,
private alert: AlertService,
- private utils: UtilsService,
) {
this.load();
this.confSubject = <BehaviorSubject<IConfig>>new BehaviorSubject(this.confStore);
@@ -59,4 +57,5 @@ export class ConfigService {
this.confStore.projectsRootDir = p;
this.save();
}
+
}
diff --git a/webapp/src/app/services/project.service.spec.ts b/webapp/src/app/@core-xds/services/project.service.spec.ts
index b8edfc7..b8edfc7 100644
--- a/webapp/src/app/services/project.service.spec.ts
+++ b/webapp/src/app/@core-xds/services/project.service.spec.ts
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/services/sdk.service.ts b/webapp/src/app/@core-xds/services/sdk.service.ts
index d492774..d1daa86 100644
--- a/webapp/src/app/services/sdk.service.ts
+++ b/webapp/src/app/@core-xds/services/sdk.service.ts
@@ -4,6 +4,8 @@ import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { XDSAgentService } from '../services/xdsagent.service';
+import 'rxjs/add/observable/throw';
+
export interface ISdk {
id: string;
profile: string;
@@ -51,4 +53,14 @@ export class SdkService {
}
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);
+ }
+}
diff --git a/webapp/src/app/@core/data/.gitkeep b/webapp/src/app/@core/data/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/webapp/src/app/@core/data/.gitkeep
diff --git a/webapp/src/app/@core/data/state.service.ts b/webapp/src/app/@core/data/state.service.ts
new file mode 100644
index 0000000..a6bcb08
--- /dev/null
+++ b/webapp/src/app/@core/data/state.service.ts
@@ -0,0 +1,69 @@
+import { Injectable } from '@angular/core';
+
+import { Observable } from 'rxjs/Observable';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+import 'rxjs/add/observable/of';
+
+@Injectable()
+export class StateService {
+
+ protected layouts: any = [
+ {
+ name: 'One Column',
+ icon: 'nb-layout-default',
+ id: 'one-column',
+ selected: true,
+ },
+ {
+ name: 'Two Column',
+ icon: 'nb-layout-two-column',
+ id: 'two-column',
+ },
+ {
+ name: 'Center Column',
+ icon: 'nb-layout-centre',
+ id: 'center-column',
+ },
+ ];
+
+ protected sidebars: any = [
+ {
+ name: 'Left Sidebar',
+ icon: 'nb-layout-sidebar-left',
+ id: 'left',
+ selected: true,
+ },
+ {
+ name: 'Right Sidebar',
+ icon: 'nb-layout-sidebar-right',
+ id: 'right',
+ },
+ ];
+
+ protected layoutState$ = new BehaviorSubject(this.layouts[0]);
+ protected sidebarState$ = new BehaviorSubject(this.sidebars[0]);
+
+ setLayoutState(state: any): any {
+ this.layoutState$.next(state);
+ }
+
+ getLayoutStates(): Observable<any[]> {
+ return Observable.of(this.layouts);
+ }
+
+ onLayoutState(): Observable<any> {
+ return this.layoutState$.asObservable();
+ }
+
+ setSidebarState(state: any): any {
+ this.sidebarState$.next(state);
+ }
+
+ getSidebarStates(): Observable<any[]> {
+ return Observable.of(this.sidebars);
+ }
+
+ onSidebarState(): Observable<any> {
+ return this.sidebarState$.asObservable();
+ }
+}
diff --git a/webapp/src/app/@core/utils/.gitkeep b/webapp/src/app/@core/utils/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/webapp/src/app/@core/utils/.gitkeep
diff --git a/webapp/src/app/@core/utils/analytics.service.ts b/webapp/src/app/@core/utils/analytics.service.ts
new file mode 100644
index 0000000..73f1332
--- /dev/null
+++ b/webapp/src/app/@core/utils/analytics.service.ts
@@ -0,0 +1,31 @@
+import { Injectable } from '@angular/core';
+import { NavigationEnd, Router } from '@angular/router';
+import { Location } from '@angular/common';
+
+import { filter } from 'rxjs/operator/filter';
+
+declare const ga: any;
+
+@Injectable()
+export class AnalyticsService {
+ private enabled: boolean;
+
+ constructor(private location: Location, private router: Router) {
+ this.enabled = false;
+ }
+
+ trackPageViews() {
+ if (this.enabled) {
+ filter.call(this.router.events, (event) => event instanceof NavigationEnd)
+ .subscribe(() => {
+ ga('send', {hitType: 'pageview', page: this.location.path()});
+ });
+ }
+ }
+
+ trackEvent(eventName: string) {
+ if (this.enabled) {
+ ga('send', 'event', eventName);
+ }
+ }
+}
diff --git a/webapp/src/app/@theme/components/footer/footer.component.scss b/webapp/src/app/@theme/components/footer/footer.component.scss
new file mode 100644
index 0000000..78d8114
--- /dev/null
+++ b/webapp/src/app/@theme/components/footer/footer.component.scss
@@ -0,0 +1,30 @@
+@import '../../styles/themes';
+@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
+@import '~bootstrap/scss/mixins/breakpoints';
+
+@include nb-install-component() {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ .socials {
+ font-size: 2rem;
+
+ a {
+ padding: 0.4rem;
+ color: nb-theme(color-fg);
+ transition: color ease-out 0.1s;
+
+ &:hover {
+ color: nb-theme(color-fg-heading);
+ }
+ }
+ }
+
+ @include media-breakpoint-down(is) {
+ .socials {
+ font-size: 1.5rem;
+ }
+ }
+}
diff --git a/webapp/src/app/@theme/components/footer/footer.component.ts b/webapp/src/app/@theme/components/footer/footer.component.ts
new file mode 100644
index 0000000..8e1e825
--- /dev/null
+++ b/webapp/src/app/@theme/components/footer/footer.component.ts
@@ -0,0 +1,23 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'ngx-footer',
+ styleUrls: ['./footer.component.scss'],
+ template: `
+ <span class="created-by">Created by
+ <b><a href="http://iot.bzh" target="_blank">IoT.bzh</a></b> 2017
+ &nbsp;&nbsp;
+ <span style="font-size: small;">(powered by <a href="https://github.com/akveo/ngx-admin" target="_blank">akveo/ngx-admin</a>)</span>
+ </span>
+ <!-- MODS_XDS
+ <div class="socials">
+ <a href="#" target="_blank" class="ion ion-social-github"></a>
+ <a href="#" target="_blank" class="ion ion-social-facebook"></a>
+ <a href="#" target="_blank" class="ion ion-social-twitter"></a>
+ <a href="#" target="_blank" class="ion ion-social-linkedin"></a>
+ </div>
+ -->
+ `,
+})
+export class FooterComponent {
+}
diff --git a/webapp/src/app/@theme/components/header/header.component.html b/webapp/src/app/@theme/components/header/header.component.html
new file mode 100644
index 0000000..5d5eff6
--- /dev/null
+++ b/webapp/src/app/@theme/components/header/header.component.html
@@ -0,0 +1,32 @@
+<div class="header-container"
+ [class.left]="position === 'normal'"
+ [class.right]="position === 'inverse'">
+ <div class="logo-containter">
+ <a (click)="toggleSidebar()" href="#" class="navigation"><i class="nb-menu"></i></a>
+<!-- MODS_XDS
+ <div class="logo" (click)="goToHome()">ngx-<span>admin</span></div>
+-->
+ <div class="logo" (click)="goToHome()">XDS <span>dashboard</span></div>
+ </div>
+<!-- MODS_XDS
+ <ngx-theme-switcher></ngx-theme-switcher>
+-->
+</div>
+
+<nb-actions
+ size="medium"
+ class="header-container"
+ [class.right]="position === 'normal'"
+ [class.left]="position === 'inverse'">
+ <nb-action icon="nb-grid-b" class="toggle-layout" (click)="toggleSettings()"></nb-action>
+ <nb-action>
+ <nb-user [menu]="userMenu" [name]="user?.name" [picture]="user?.picture"></nb-user>
+ </nb-action>
+ <nb-action class="control-item" disabled icon="nb-notifications"></nb-action>
+<!-- MODS_XDS
+ <nb-action class="control-item" icon="nb-email"></nb-action>
+-->
+ <nb-action class="control-item">
+ <nb-search type="rotate-layout" (click)="startSearch()"></nb-search>
+ </nb-action>
+</nb-actions>
diff --git a/webapp/src/app/@theme/components/header/header.component.scss b/webapp/src/app/@theme/components/header/header.component.scss
new file mode 100644
index 0000000..647311b
--- /dev/null
+++ b/webapp/src/app/@theme/components/header/header.component.scss
@@ -0,0 +1,115 @@
+@import '../../styles/themes';
+@import '~bootstrap/scss/mixins/breakpoints';
+@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
+
+@include nb-install-component() {
+ display: flex;
+ justify-content: space-between;
+ width: 100%;
+
+ .left {
+ display: flex;
+ width: 100%;
+ order: 0;
+ flex-direction: row;
+ }
+ .right {
+ order: 1;
+ flex-direction: row-reverse;
+ }
+
+ .logo-containter {
+ display: flex;
+ align-items: center;
+ }
+
+ .control-item {
+ display: block;
+ }
+
+ .header-container {
+ display: flex;
+ align-items: center;
+ width: 100%;
+
+ .navigation {
+ padding-right: nb-theme(padding);
+ font-size: 2.5rem;
+ text-decoration: none;
+
+ i {
+ display: block;
+ }
+
+ }
+
+ .logo {
+ padding: 0 nb-theme(padding);
+ font-size: 1.75rem;
+ font-weight: nb-theme(font-weight-bolder);
+ border-left: 1px solid nb-theme(separator);
+ white-space: nowrap;
+
+ span {
+ font-weight: nb-theme(font-weight-normal);
+ }
+ }
+ }
+
+ .toggle-layout /deep/ a {
+ display: block;
+ text-decoration: none;
+ line-height: 1;
+
+ i {
+ color: nb-theme(color-fg-highlight);
+ font-size: 2.25rem;
+ }
+ }
+
+ @include media-breakpoint-down(md) {
+
+ nb-action:not(.toggle-layout) {
+ border: none;
+ }
+
+ .control-item {
+ display: none;
+ }
+
+ .toggle-layout {
+ padding: 0;
+ }
+ }
+
+ @include media-breakpoint-down(sm) {
+
+ nb-user /deep/ .user-name {
+ display: none;
+ }
+ }
+
+ @include media-breakpoint-down(is) {
+
+ .header-container {
+ .logo {
+ font-size: 1.25rem;
+ }
+ }
+
+ .toggle-layout {
+ display: none;
+ }
+
+ nb-action:not(.toggle-layout) {
+ padding: 0;
+ }
+ }
+
+ @include media-breakpoint-down(xs) {
+ .right /deep/ {
+ display: none;
+ }
+ }
+}
+
diff --git a/webapp/src/app/@theme/components/header/header.component.ts b/webapp/src/app/@theme/components/header/header.component.ts
new file mode 100644
index 0000000..e2a84cb
--- /dev/null
+++ b/webapp/src/app/@theme/components/header/header.component.ts
@@ -0,0 +1,51 @@
+import { Component, Input, OnInit } from '@angular/core';
+
+import { NbMenuService, NbSidebarService } from '@nebular/theme';
+// XDS_MODS
+import { UserService } from '../../../@core-xds/services/users.service';
+import { AnalyticsService } from '../../../@core/utils/analytics.service';
+
+@Component({
+ selector: 'ngx-header',
+ styleUrls: ['./header.component.scss'],
+ templateUrl: './header.component.html',
+})
+export class HeaderComponent implements OnInit {
+
+
+ @Input() position = 'normal';
+
+ user: any;
+
+ userMenu = [{ title: 'Profile' }, { title: 'Log out' }];
+
+ constructor(private sidebarService: NbSidebarService,
+ private menuService: NbMenuService,
+ private userService: UserService,
+ private analyticsService: AnalyticsService) {
+ }
+
+ ngOnInit() {
+ // XDS_MODS
+ this.userService.getUsers()
+ .subscribe((users: any) => this.user = users.anonymous);
+ }
+
+ toggleSidebar(): boolean {
+ this.sidebarService.toggle(true, 'menu-sidebar');
+ return false;
+ }
+
+ toggleSettings(): boolean {
+ this.sidebarService.toggle(false, 'settings-sidebar');
+ return false;
+ }
+
+ goToHome() {
+ this.menuService.navigateHome();
+ }
+
+ startSearch() {
+ this.analyticsService.trackEvent('startSearch');
+ }
+}
diff --git a/webapp/src/app/@theme/components/index.ts b/webapp/src/app/@theme/components/index.ts
new file mode 100644
index 0000000..4a25efe
--- /dev/null
+++ b/webapp/src/app/@theme/components/index.ts
@@ -0,0 +1,6 @@
+export * from './header/header.component';
+export * from './footer/footer.component';
+export * from './search-input/search-input.component';
+// XDS_MODS export * from './tiny-mce/tiny-mce.component';
+export * from './theme-settings/theme-settings.component';
+export * from './theme-switcher/theme-switcher.component';
diff --git a/webapp/src/app/@theme/components/search-input/search-input.component.scss b/webapp/src/app/@theme/components/search-input/search-input.component.scss
new file mode 100644
index 0000000..5ef07ef
--- /dev/null
+++ b/webapp/src/app/@theme/components/search-input/search-input.component.scss
@@ -0,0 +1,33 @@
+:host {
+ display: flex;
+ align-items: center;
+
+ i.control-icon {
+ &::before {
+ font-size: 2.3rem;
+ }
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+
+ input {
+ border: none;
+ outline: none;
+ margin-left: 1rem;
+ width: 15rem;
+ transition: width 0.2s ease;
+
+ &.hidden {
+ width: 0;
+ margin: 0;
+ }
+ }
+
+ /deep/ search-input {
+ input {
+ background: transparent;
+ }
+ }
+}
diff --git a/webapp/src/app/@theme/components/search-input/search-input.component.ts b/webapp/src/app/@theme/components/search-input/search-input.component.ts
new file mode 100644
index 0000000..d9f0f10
--- /dev/null
+++ b/webapp/src/app/@theme/components/search-input/search-input.component.ts
@@ -0,0 +1,35 @@
+import { Component, ElementRef, EventEmitter, Output, ViewChild } from '@angular/core';
+
+@Component({
+ selector: 'ngx-search-input',
+ styleUrls: ['./search-input.component.scss'],
+ template: `
+ <i class="control-icon ion ion-ios-search"
+ (click)="showInput()"></i>
+ <input placeholder="Type your search request here..."
+ #input
+ [class.hidden]="!isInputShown"
+ (blur)="hideInput()"
+ (input)="onInput($event)">
+ `,
+})
+export class SearchInputComponent {
+ @ViewChild('input') input: ElementRef;
+
+ @Output() search: EventEmitter<string> = new EventEmitter<string>();
+
+ isInputShown = false;
+
+ showInput() {
+ this.isInputShown = true;
+ this.input.nativeElement.focus();
+ }
+
+ hideInput() {
+ this.isInputShown = false;
+ }
+
+ onInput(val: string) {
+ this.search.emit(val);
+ }
+}
diff --git a/webapp/src/app/@theme/components/theme-settings/theme-settings.component.scss b/webapp/src/app/@theme/components/theme-settings/theme-settings.component.scss
new file mode 100644
index 0000000..4a0a93e
--- /dev/null
+++ b/webapp/src/app/@theme/components/theme-settings/theme-settings.component.scss
@@ -0,0 +1,36 @@
+@import '../../styles/themes';
+
+@include nb-install-component() {
+ h6 {
+ margin-bottom: 0.5rem;
+ }
+
+ .settings-row {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+
+ width: 90%;
+ margin: 0 0 1rem;
+
+ a {
+ text-decoration: none;
+ font-size: 2.25rem;
+
+ color: nb-theme(color-fg);
+
+ &.selected {
+ color: nb-theme(color-success);
+ }
+
+ @include nb-for-theme(cosmic) {
+ &.selected {
+ color: nb-theme(link-color);
+ }
+ }
+ }
+ }
+}
+
diff --git a/webapp/src/app/@theme/components/theme-settings/theme-settings.component.ts b/webapp/src/app/@theme/components/theme-settings/theme-settings.component.ts
new file mode 100644
index 0000000..9cd60fe
--- /dev/null
+++ b/webapp/src/app/@theme/components/theme-settings/theme-settings.component.ts
@@ -0,0 +1,65 @@
+import { Component, Input, OnInit } from '@angular/core';
+
+import { StateService } from '../../../@core/data/state.service';
+
+@Component({
+ selector: 'ngx-theme-settings',
+ styleUrls: ['./theme-settings.component.scss'],
+ template: `
+ <h6>LAYOUTS</h6>
+ <div class="settings-row">
+ <a *ngFor="let layout of layouts"
+ href="#"
+ [class.selected]="layout.selected"
+ [attr.title]="layout.name"
+ (click)="layoutSelect(layout)">
+ <i [attr.class]="layout.icon"></i>
+ </a>
+ </div>
+ <h6>SIDEBAR</h6>
+ <div class="settings-row">
+ <a *ngFor="let sidebar of sidebars"
+ href="#"
+ [class.selected]="sidebar.selected"
+ [attr.title]="sidebar.name"
+ (click)="sidebarSelect(sidebar)">
+ <i [attr.class]="sidebar.icon"></i>
+ </a>
+ </div>
+ `,
+})
+export class ThemeSettingsComponent {
+
+ layouts = [];
+ sidebars = [];
+
+ constructor(protected stateService: StateService) {
+ this.stateService.getLayoutStates()
+ .subscribe((layouts: any[]) => this.layouts = layouts);
+
+ this.stateService.getSidebarStates()
+ .subscribe((sidebars: any[]) => this.sidebars = sidebars);
+ }
+
+ layoutSelect(layout: any): boolean {
+ this.layouts = this.layouts.map((l: any) => {
+ l.selected = false;
+ return l;
+ });
+
+ layout.selected = true;
+ this.stateService.setLayoutState(layout);
+ return false;
+ }
+
+ sidebarSelect(sidebars: any): boolean {
+ this.sidebars = this.sidebars.map((s: any) => {
+ s.selected = false;
+ return s;
+ });
+
+ sidebars.selected = true;
+ this.stateService.setSidebarState(sidebars);
+ return false;
+ }
+}
diff --git a/webapp/src/app/@theme/components/theme-switcher/theme-switcher.component.scss b/webapp/src/app/@theme/components/theme-switcher/theme-switcher.component.scss
new file mode 100644
index 0000000..210add8
--- /dev/null
+++ b/webapp/src/app/@theme/components/theme-switcher/theme-switcher.component.scss
@@ -0,0 +1,101 @@
+@import '../../styles/themes';
+@import '~@nebular/theme/styles/global/bootstrap/hero-buttons';
+@import '~bootstrap/scss/mixins/breakpoints';
+@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
+
+@include nb-install-component() {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 50%;
+
+ .theme-switch {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ cursor: pointer;
+ margin: 0;
+
+ & > span {
+ font-size: 1.125rem;
+ font-weight: nb-theme(font-weight-bold);
+ transition: opacity 0.3s ease;
+
+ &.light {
+ color: nb-theme(color-fg-text);
+ padding-right: 10px;
+ }
+
+ &.cosmic {
+ color: nb-theme(color-fg);
+ padding-left: 10px;
+ }
+
+ @include nb-for-theme(cosmic) {
+ &.light {
+ color: nb-theme(color-fg);
+ }
+
+ &.cosmic {
+ color: nb-theme(color-white);
+ }
+ }
+
+ &:active {
+ opacity: 0.78;
+ }
+ }
+ }
+
+ .switch {
+ position: relative;
+ display: inline-block;
+ width: 4rem;
+ height: 1.75rem;
+ margin: 0;
+
+ input {
+ display: none;
+
+ &:checked + .slider::before {
+ transform: translateX(2.25rem);
+ }
+ }
+
+ .slider {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ border-radius: 1.75rem;
+ background-color: nb-theme(layout-bg);
+ }
+
+ .slider::before {
+ position: absolute;
+ content: '';
+ height: 1.75rem;
+ width: 1.75rem;
+ border-radius: 50%;
+ background-color: nb-theme(color-success);
+ transition: 0.2s;
+
+ box-shadow: 0 0 0.25rem 0 rgba(nb-theme(color-fg), 0.4);
+
+ @include nb-for-theme(cosmic) {
+ @include btn-hero-primary-gradient();
+ }
+ }
+ }
+
+ @include media-breakpoint-down(is) {
+ .light, .cosmic {
+ display: none;
+ }
+ }
+
+ @include media-breakpoint-down(xs) {
+ align-items: flex-end;
+ }
+}
diff --git a/webapp/src/app/@theme/components/theme-switcher/theme-switcher.component.ts b/webapp/src/app/@theme/components/theme-switcher/theme-switcher.component.ts
new file mode 100644
index 0000000..e84b942
--- /dev/null
+++ b/webapp/src/app/@theme/components/theme-switcher/theme-switcher.component.ts
@@ -0,0 +1,48 @@
+import { Component, OnInit } from '@angular/core';
+import { NbThemeService } from '@nebular/theme';
+import { NbJSThemeOptions } from '@nebular/theme/services/js-themes/theme.options';
+import { AnalyticsService } from '../../../@core/utils/analytics.service';
+
+@Component({
+ selector: 'ngx-theme-switcher',
+ styleUrls: ['./theme-switcher.component.scss'],
+ template: `
+ <label class="theme-switch">
+ <span class="light">Light</span>
+ <div class="switch">
+ <input type="checkbox" [checked]="currentBoolTheme()" (change)="toggleTheme(theme.checked)" #theme>
+ <span class="slider"></span>
+ </div>
+ <span class="cosmic">Cosmic</span>
+ </label>
+ `,
+})
+export class ThemeSwitcherComponent implements OnInit {
+ theme: NbJSThemeOptions;
+
+ constructor(private themeService: NbThemeService, private analyticsService: AnalyticsService) {
+ }
+
+ ngOnInit() {
+ this.themeService.getJsTheme()
+ .subscribe((theme: NbJSThemeOptions) => this.theme = theme);
+ }
+
+ toggleTheme(theme: boolean) {
+ const boolTheme = this.boolToTheme(theme);
+ this.themeService.changeTheme(boolTheme);
+ this.analyticsService.trackEvent('switchTheme');
+ }
+
+ currentBoolTheme() {
+ return this.themeToBool(this.theme);
+ }
+
+ private themeToBool(theme: NbJSThemeOptions) {
+ return theme.name === 'cosmic';
+ }
+
+ private boolToTheme(theme: boolean) {
+ return theme ? 'cosmic' : 'default';
+ }
+}
diff --git a/webapp/src/app/@theme/components/tiny-mce/tiny-mce.component.ts b/webapp/src/app/@theme/components/tiny-mce/tiny-mce.component.ts
new file mode 100644
index 0000000..c54685b
--- /dev/null
+++ b/webapp/src/app/@theme/components/tiny-mce/tiny-mce.component.ts
@@ -0,0 +1,33 @@
+import { Component, OnDestroy, AfterViewInit, Output, EventEmitter, ElementRef } from '@angular/core';
+
+@Component({
+ selector: 'ngx-tiny-mce',
+ template: '',
+})
+export class TinyMCEComponent implements OnDestroy, AfterViewInit {
+
+ @Output() editorKeyup = new EventEmitter<any>();
+
+ editor: any;
+
+ constructor(private host: ElementRef) { }
+
+ ngAfterViewInit() {
+ tinymce.init({
+ target: this.host.nativeElement,
+ plugins: ['link', 'paste', 'table'],
+ skin_url: 'assets/skins/lightgray',
+ setup: editor => {
+ this.editor = editor;
+ editor.on('keyup', () => {
+ this.editorKeyup.emit(editor.getContent());
+ });
+ },
+ height: '320',
+ });
+ }
+
+ ngOnDestroy() {
+ tinymce.remove(this.editor);
+ }
+}
diff --git a/webapp/src/app/@theme/directives/.gitkeep b/webapp/src/app/@theme/directives/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/webapp/src/app/@theme/directives/.gitkeep
diff --git a/webapp/src/app/@theme/layouts/index.ts b/webapp/src/app/@theme/layouts/index.ts
new file mode 100644
index 0000000..47d2015
--- /dev/null
+++ b/webapp/src/app/@theme/layouts/index.ts
@@ -0,0 +1,6 @@
+export * from './one-column/one-column.layout';
+export * from './two-columns/two-columns.layout';
+export * from './three-columns/three-columns.layout';
+export * from './sample/sample.layout';
+// XDS_MODS
+export * from './xds/xds.layout';
diff --git a/webapp/src/app/@theme/layouts/one-column/one-column.layout.scss b/webapp/src/app/@theme/layouts/one-column/one-column.layout.scss
new file mode 100644
index 0000000..7ccf7b7
--- /dev/null
+++ b/webapp/src/app/@theme/layouts/one-column/one-column.layout.scss
@@ -0,0 +1,130 @@
+@import '../../styles/themes';
+@import '~bootstrap/scss/mixins/breakpoints';
+@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
+
+@include nb-install-component() {
+ nb-layout-column.small {
+ flex: 0.15 !important;
+ }
+
+ nb-sidebar.settings-sidebar {
+ $sidebar-width: 7.5rem;
+
+ transition: width 0.3s ease;
+ width: $sidebar-width;
+ overflow: hidden;
+
+ &.collapsed {
+ width: 0;
+
+ /deep/ .main-container {
+ width: 0;
+
+ .scrollable {
+ width: $sidebar-width;
+ padding: 1.25rem;
+ }
+ }
+ }
+
+ /deep/ .main-container {
+ width: $sidebar-width;
+ background: nb-theme(color-bg);
+ transition: width 0.3s ease;
+ overflow: hidden;
+
+ .scrollable {
+ width: $sidebar-width;
+ }
+
+ @include nb-for-theme(cosmic) {
+ background: nb-theme(layout-bg);
+ }
+ }
+ }
+
+ nb-sidebar.menu-sidebar {
+
+ margin-top: nb-theme(sidebar-header-gap);
+
+ /deep/ .main-container {
+ height:
+ calc(#{nb-theme(sidebar-height)} - #{nb-theme(header-height)} - #{nb-theme(sidebar-header-gap)}) !important;
+ border-top-right-radius: nb-theme(radius);
+ }
+
+ /deep/ nb-sidebar-header {
+ padding-bottom: 0.5rem;
+ text-align: center;
+ }
+
+ background: transparent;
+
+ .main-btn {
+ padding: 0.75rem 2.5rem;
+ margin-top: -2rem;
+ font-weight: bold;
+ transition: padding 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.48);
+
+ i {
+ font-size: 2rem;
+ text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
+ }
+ span {
+ padding-left: 0.25rem;
+ }
+
+ i, span {
+ vertical-align: middle;
+ }
+ }
+
+ &.compacted {
+
+ /deep/ nb-sidebar-header {
+ padding-left: 0;
+ padding-right: 0;
+ }
+
+ .main-btn {
+ width: 46px;
+ height: 44px;
+ padding: 0.375rem;
+ border-radius: 5px;
+ transition: none;
+
+ span {
+ display: none;
+ }
+ }
+ }
+ }
+
+ @include media-breakpoint-down(xs) {
+ .main-content {
+ padding: 0.75rem !important;
+
+ }
+ }
+
+ @include media-breakpoint-down(sm) {
+
+ nb-sidebar.menu-sidebar {
+
+ margin-top: 0;
+
+ /deep/ .main-container {
+ height: calc(#{nb-theme(sidebar-height)} - #{nb-theme(header-height)}) !important;
+ border-top-right-radius: 0;
+
+ .scrollable {
+ padding-top: 0;
+ }
+ }
+ }
+
+ .main-btn {
+ display: none;
+ }
+ }
+}
diff --git a/webapp/src/app/@theme/layouts/one-column/one-column.layout.ts b/webapp/src/app/@theme/layouts/one-column/one-column.layout.ts
new file mode 100644
index 0000000..beeaf30
--- /dev/null
+++ b/webapp/src/app/@theme/layouts/one-column/one-column.layout.ts
@@ -0,0 +1,35 @@
+import { Component } from '@angular/core';
+
+// TODO: move layouts into the framework
+@Component({
+ selector: 'ngx-one-column-layout',
+ styleUrls: ['./one-column.layout.scss'],
+ template: `
+ <nb-layout>
+ <nb-layout-header fixed>
+ <ngx-header></ngx-header>
+ </nb-layout-header>
+
+ <nb-sidebar class="menu-sidebar" tag="menu-sidebar" responsive>
+ <nb-sidebar-header>
+ <!-- XDS_MODS
+ <a href="#" class="btn btn-hero-success main-btn">
+ <i class="ion ion-social-github"></i> <span>Support Us</span>
+ </a>
+ -->
+ </nb-sidebar-header>
+ <ng-content select="nb-menu"></ng-content>
+ </nb-sidebar>
+
+ <nb-layout-column>
+ <ng-content select="router-outlet"></ng-content>
+ </nb-layout-column>
+
+ <nb-layout-footer fixed>
+ <ngx-footer></ngx-footer>
+ </nb-layout-footer>
+ </nb-layout>
+ `,
+})
+export class OneColumnLayoutComponent {
+}
diff --git a/webapp/src/app/@theme/layouts/sample/sample.layout.scss b/webapp/src/app/@theme/layouts/sample/sample.layout.scss
new file mode 100644
index 0000000..7ccf7b7
--- /dev/null
+++ b/webapp/src/app/@theme/layouts/sample/sample.layout.scss
@@ -0,0 +1,130 @@
+@import '../../styles/themes';
+@import '~bootstrap/scss/mixins/breakpoints';
+@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
+
+@include nb-install-component() {
+ nb-layout-column.small {
+ flex: 0.15 !important;
+ }
+
+ nb-sidebar.settings-sidebar {
+ $sidebar-width: 7.5rem;
+
+ transition: width 0.3s ease;
+ width: $sidebar-width;
+ overflow: hidden;
+
+ &.collapsed {
+ width: 0;
+
+ /deep/ .main-container {
+ width: 0;
+
+ .scrollable {
+ width: $sidebar-width;
+ padding: 1.25rem;
+ }
+ }
+ }
+
+ /deep/ .main-container {
+ width: $sidebar-width;
+ background: nb-theme(color-bg);
+ transition: width 0.3s ease;
+ overflow: hidden;
+
+ .scrollable {
+ width: $sidebar-width;
+ }
+
+ @include nb-for-theme(cosmic) {
+ background: nb-theme(layout-bg);
+ }
+ }
+ }
+
+ nb-sidebar.menu-sidebar {
+
+ margin-top: nb-theme(sidebar-header-gap);
+
+ /deep/ .main-container {
+ height:
+ calc(#{nb-theme(sidebar-height)} - #{nb-theme(header-height)} - #{nb-theme(sidebar-header-gap)}) !important;
+ border-top-right-radius: nb-theme(radius);
+ }
+
+ /deep/ nb-sidebar-header {
+ padding-bottom: 0.5rem;
+ text-align: center;
+ }
+
+ background: transparent;
+
+ .main-btn {
+ padding: 0.75rem 2.5rem;
+ margin-top: -2rem;
+ font-weight: bold;
+ transition: padding 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.48);
+
+ i {
+ font-size: 2rem;
+ text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
+ }
+ span {
+ padding-left: 0.25rem;
+ }
+
+ i, span {
+ vertical-align: middle;
+ }
+ }
+
+ &.compacted {
+
+ /deep/ nb-sidebar-header {
+ padding-left: 0;
+ padding-right: 0;
+ }
+
+ .main-btn {
+ width: 46px;
+ height: 44px;
+ padding: 0.375rem;
+ border-radius: 5px;
+ transition: none;
+
+ span {
+ display: none;
+ }
+ }
+ }
+ }
+
+ @include media-breakpoint-down(xs) {
+ .main-content {
+ padding: 0.75rem !important;
+
+ }
+ }
+
+ @include media-breakpoint-down(sm) {
+
+ nb-sidebar.menu-sidebar {
+
+ margin-top: 0;
+
+ /deep/ .main-container {
+ height: calc(#{nb-theme(sidebar-height)} - #{nb-theme(header-height)}) !important;
+ border-top-right-radius: 0;
+
+ .scrollable {
+ padding-top: 0;
+ }
+ }
+ }
+
+ .main-btn {
+ display: none;
+ }
+ }
+}
diff --git a/webapp/src/app/@theme/layouts/sample/sample.layout.ts b/webapp/src/app/@theme/layouts/sample/sample.layout.ts
new file mode 100644
index 0000000..6d35c09
--- /dev/null
+++ b/webapp/src/app/@theme/layouts/sample/sample.layout.ts
@@ -0,0 +1,145 @@
+import { Component, OnDestroy } from '@angular/core';
+import {
+ NbMediaBreakpoint,
+ NbMediaBreakpointsService,
+ NbMenuItem,
+ NbMenuService,
+ NbSidebarService,
+ NbThemeService,
+} from '@nebular/theme';
+
+import { StateService } from '../../../@core/data/state.service';
+
+import { Subscription } from 'rxjs/Subscription';
+import 'rxjs/add/operator/withLatestFrom';
+import 'rxjs/add/operator/delay';
+
+// TODO: move layouts into the framework
+@Component({
+ selector: 'ngx-sample-layout',
+ styleUrls: ['./sample.layout.scss'],
+ template: `
+ <nb-layout [center]="layout.id === 'center-column'" windowMode>
+ <nb-layout-header fixed>
+ <ngx-header [position]="sidebar.id === 'left' ? 'normal': 'inverse'"></ngx-header>
+ </nb-layout-header>
+
+ <nb-sidebar class="menu-sidebar"
+ tag="menu-sidebar"
+ responsive
+ [right]="sidebar.id === 'right'">
+ <nb-sidebar-header>
+ <a href="#" class="btn btn-hero-success main-btn">
+ <i class="ion ion-social-github"></i> <span>Support Us</span>
+ </a>
+ </nb-sidebar-header>
+ <ng-content select="nb-menu"></ng-content>
+ </nb-sidebar>
+
+ <nb-layout-column class="main-content">
+ <ng-content select="router-outlet"></ng-content>
+ </nb-layout-column>
+
+ <nb-layout-column left class="small" *ngIf="layout.id === 'two-column' || layout.id === 'three-column'">
+ <nb-menu [items]="subMenu"></nb-menu>
+ </nb-layout-column>
+
+ <nb-layout-column right class="small" *ngIf="layout.id === 'three-column'">
+ <nb-menu [items]="subMenu"></nb-menu>
+ </nb-layout-column>
+
+ <nb-layout-footer fixed>
+ <ngx-footer></ngx-footer>
+ </nb-layout-footer>
+
+ <nb-sidebar class="settings-sidebar"
+ tag="settings-sidebar"
+ state="collapsed"
+ fixed
+ [right]="sidebar.id !== 'right'">
+ <ngx-theme-settings></ngx-theme-settings>
+ </nb-sidebar>
+ </nb-layout>
+ `,
+})
+export class SampleLayoutComponent implements OnDestroy {
+
+ subMenu: NbMenuItem[] = [
+ {
+ title: 'PAGE LEVEL MENU',
+ group: true,
+ },
+ {
+ title: 'Buttons',
+ icon: 'ion ion-android-radio-button-off',
+ link: '/pages/ui-features/buttons',
+ },
+ {
+ title: 'Grid',
+ icon: 'ion ion-android-radio-button-off',
+ link: '/pages/ui-features/grid',
+ },
+ {
+ title: 'Icons',
+ icon: 'ion ion-android-radio-button-off',
+ link: '/pages/ui-features/icons',
+ },
+ {
+ title: 'Modals',
+ icon: 'ion ion-android-radio-button-off',
+ link: '/pages/ui-features/modals',
+ },
+ {
+ title: 'Typography',
+ icon: 'ion ion-android-radio-button-off',
+ link: '/pages/ui-features/typography',
+ },
+ {
+ title: 'Animated Searches',
+ icon: 'ion ion-android-radio-button-off',
+ link: '/pages/ui-features/search-fields',
+ },
+ {
+ title: 'Tabs',
+ icon: 'ion ion-android-radio-button-off',
+ link: '/pages/ui-features/tabs',
+ },
+ ];
+ layout: any = {};
+ sidebar: any = {};
+
+ protected layoutState$: Subscription;
+ protected sidebarState$: Subscription;
+ protected menuClick$: Subscription;
+
+ constructor(protected stateService: StateService,
+ protected menuService: NbMenuService,
+ protected themeService: NbThemeService,
+ protected bpService: NbMediaBreakpointsService,
+ protected sidebarService: NbSidebarService) {
+ this.layoutState$ = this.stateService.onLayoutState()
+ .subscribe((layout: string) => this.layout = layout);
+
+ this.sidebarState$ = this.stateService.onSidebarState()
+ .subscribe((sidebar: string) => {
+ this.sidebar = sidebar;
+ });
+
+ const isBp = this.bpService.getByName('is');
+ this.menuClick$ = this.menuService.onItemSelect()
+ .withLatestFrom(this.themeService.onMediaQueryChange())
+ .delay(20)
+ .subscribe(([item, [bpFrom, bpTo]]: [any, [NbMediaBreakpoint, NbMediaBreakpoint]]) => {
+
+ if (bpTo.width <= isBp.width) {
+ this.sidebarService.collapse('menu-sidebar');
+ }
+ });
+ }
+
+ ngOnDestroy() {
+ this.layoutState$.unsubscribe();
+ this.sidebarState$.unsubscribe();
+ this.menuClick$.unsubscribe();
+ }
+}
diff --git a/webapp/src/app/@theme/layouts/three-columns/three-columns.layout.scss b/webapp/src/app/@theme/layouts/three-columns/three-columns.layout.scss
new file mode 100644
index 0000000..7ccf7b7
--- /dev/null
+++ b/webapp/src/app/@theme/layouts/three-columns/three-columns.layout.scss
@@ -0,0 +1,130 @@
+@import '../../styles/themes';
+@import '~bootstrap/scss/mixins/breakpoints';
+@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
+
+@include nb-install-component() {
+ nb-layout-column.small {
+ flex: 0.15 !important;
+ }
+
+ nb-sidebar.settings-sidebar {
+ $sidebar-width: 7.5rem;
+
+ transition: width 0.3s ease;
+ width: $sidebar-width;
+ overflow: hidden;
+
+ &.collapsed {
+ width: 0;
+
+ /deep/ .main-container {
+ width: 0;
+
+ .scrollable {
+ width: $sidebar-width;
+ padding: 1.25rem;
+ }
+ }
+ }
+
+ /deep/ .main-container {
+ width: $sidebar-width;
+ background: nb-theme(color-bg);
+ transition: width 0.3s ease;
+ overflow: hidden;
+
+ .scrollable {
+ width: $sidebar-width;
+ }
+
+ @include nb-for-theme(cosmic) {
+ background: nb-theme(layout-bg);
+ }
+ }
+ }
+
+ nb-sidebar.menu-sidebar {
+
+ margin-top: nb-theme(sidebar-header-gap);
+
+ /deep/ .main-container {
+ height:
+ calc(#{nb-theme(sidebar-height)} - #{nb-theme(header-height)} - #{nb-theme(sidebar-header-gap)}) !important;
+ border-top-right-radius: nb-theme(radius);
+ }
+
+ /deep/ nb-sidebar-header {
+ padding-bottom: 0.5rem;
+ text-align: center;
+ }
+
+ background: transparent;
+
+ .main-btn {
+ padding: 0.75rem 2.5rem;
+ margin-top: -2rem;
+ font-weight: bold;
+ transition: padding 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.48);
+
+ i {
+ font-size: 2rem;
+ text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
+ }
+ span {
+ padding-left: 0.25rem;
+ }
+
+ i, span {
+ vertical-align: middle;
+ }
+ }
+
+ &.compacted {
+
+ /deep/ nb-sidebar-header {
+ padding-left: 0;
+ padding-right: 0;
+ }
+
+ .main-btn {
+ width: 46px;
+ height: 44px;
+ padding: 0.375rem;
+ border-radius: 5px;
+ transition: none;
+
+ span {
+ display: none;
+ }
+ }
+ }
+ }
+
+ @include media-breakpoint-down(xs) {
+ .main-content {
+ padding: 0.75rem !important;
+
+ }
+ }
+
+ @include media-breakpoint-down(sm) {
+
+ nb-sidebar.menu-sidebar {
+
+ margin-top: 0;
+
+ /deep/ .main-container {
+ height: calc(#{nb-theme(sidebar-height)} - #{nb-theme(header-height)}) !important;
+ border-top-right-radius: 0;
+
+ .scrollable {
+ padding-top: 0;
+ }
+ }
+ }
+
+ .main-btn {
+ display: none;
+ }
+ }
+}
diff --git a/webapp/src/app/@theme/layouts/three-columns/three-columns.layout.ts b/webapp/src/app/@theme/layouts/three-columns/three-columns.layout.ts
new file mode 100644
index 0000000..447b23e
--- /dev/null
+++ b/webapp/src/app/@theme/layouts/three-columns/three-columns.layout.ts
@@ -0,0 +1,39 @@
+import { Component } from '@angular/core';
+
+// TODO: move layouts into the framework
+@Component({
+ selector: 'ngx-three-columns-layout',
+ styleUrls: ['./three-columns.layout.scss'],
+ template: `
+ <nb-layout>
+ <nb-layout-header fixed>
+ <ngx-header></ngx-header>
+ </nb-layout-header>
+
+ <nb-sidebar class="menu-sidebar" tag="menu-sidebar" responsive >
+ <nb-sidebar-header>
+ <a href="#" class="btn btn-hero-success main-btn">
+ <i class="ion ion-social-github"></i> <span>Support Us</span>
+ </a>
+ </nb-sidebar-header>
+ <ng-content select="nb-menu"></ng-content>
+ </nb-sidebar>
+
+ <nb-layout-column class="small">
+ </nb-layout-column>
+
+ <nb-layout-column right>
+ <ng-content select="router-outlet"></ng-content>
+ </nb-layout-column>
+
+ <nb-layout-column class="small">
+ </nb-layout-column>
+
+ <nb-layout-footer fixed>
+ <ngx-footer></ngx-footer>
+ </nb-layout-footer>
+ </nb-layout>
+ `,
+})
+export class ThreeColumnsLayoutComponent {
+}
diff --git a/webapp/src/app/@theme/layouts/two-columns/two-columns.layout.scss b/webapp/src/app/@theme/layouts/two-columns/two-columns.layout.scss
new file mode 100644
index 0000000..7ccf7b7
--- /dev/null
+++ b/webapp/src/app/@theme/layouts/two-columns/two-columns.layout.scss
@@ -0,0 +1,130 @@
+@import '../../styles/themes';
+@import '~bootstrap/scss/mixins/breakpoints';
+@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
+
+@include nb-install-component() {
+ nb-layout-column.small {
+ flex: 0.15 !important;
+ }
+
+ nb-sidebar.settings-sidebar {
+ $sidebar-width: 7.5rem;
+
+ transition: width 0.3s ease;
+ width: $sidebar-width;
+ overflow: hidden;
+
+ &.collapsed {
+ width: 0;
+
+ /deep/ .main-container {
+ width: 0;
+
+ .scrollable {
+ width: $sidebar-width;
+ padding: 1.25rem;
+ }
+ }
+ }
+
+ /deep/ .main-container {
+ width: $sidebar-width;
+ background: nb-theme(color-bg);
+ transition: width 0.3s ease;
+ overflow: hidden;
+
+ .scrollable {
+ width: $sidebar-width;
+ }
+
+ @include nb-for-theme(cosmic) {
+ background: nb-theme(layout-bg);
+ }
+ }
+ }
+
+ nb-sidebar.menu-sidebar {
+
+ margin-top: nb-theme(sidebar-header-gap);
+
+ /deep/ .main-container {
+ height:
+ calc(#{nb-theme(sidebar-height)} - #{nb-theme(header-height)} - #{nb-theme(sidebar-header-gap)}) !important;
+ border-top-right-radius: nb-theme(radius);
+ }
+
+ /deep/ nb-sidebar-header {
+ padding-bottom: 0.5rem;
+ text-align: center;
+ }
+
+ background: transparent;
+
+ .main-btn {
+ padding: 0.75rem 2.5rem;
+ margin-top: -2rem;
+ font-weight: bold;
+ transition: padding 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.48);
+
+ i {
+ font-size: 2rem;
+ text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
+ }
+ span {
+ padding-left: 0.25rem;
+ }
+
+ i, span {
+ vertical-align: middle;
+ }
+ }
+
+ &.compacted {
+
+ /deep/ nb-sidebar-header {
+ padding-left: 0;
+ padding-right: 0;
+ }
+
+ .main-btn {
+ width: 46px;
+ height: 44px;
+ padding: 0.375rem;
+ border-radius: 5px;
+ transition: none;
+
+ span {
+ display: none;
+ }
+ }
+ }
+ }
+
+ @include media-breakpoint-down(xs) {
+ .main-content {
+ padding: 0.75rem !important;
+
+ }
+ }
+
+ @include media-breakpoint-down(sm) {
+
+ nb-sidebar.menu-sidebar {
+
+ margin-top: 0;
+
+ /deep/ .main-container {
+ height: calc(#{nb-theme(sidebar-height)} - #{nb-theme(header-height)}) !important;
+ border-top-right-radius: 0;
+
+ .scrollable {
+ padding-top: 0;
+ }
+ }
+ }
+
+ .main-btn {
+ display: none;
+ }
+ }
+}
diff --git a/webapp/src/app/@theme/layouts/two-columns/two-columns.layout.ts b/webapp/src/app/@theme/layouts/two-columns/two-columns.layout.ts
new file mode 100644
index 0000000..b7f3a67
--- /dev/null
+++ b/webapp/src/app/@theme/layouts/two-columns/two-columns.layout.ts
@@ -0,0 +1,37 @@
+import { Component } from '@angular/core';
+
+// TODO: move layouts into the framework
+@Component({
+ selector: 'ngx-two-columns-layout',
+ styleUrls: ['./two-columns.layout.scss'],
+ template: `
+ <nb-layout>
+ <nb-layout-header fixed>
+ <ngx-header></ngx-header>
+ </nb-layout-header>
+
+ <nb-sidebar class="menu-sidebar" tag="menu-sidebar" responsive >
+ <nb-sidebar-header>
+ <a href="#" class="btn btn-hero-success main-btn">
+ <i class="ion ion-social-github"></i> <span>Support Us</span>
+ </a>
+ </nb-sidebar-header>
+ <ng-content select="nb-menu"></ng-content>
+ </nb-sidebar>
+
+ <nb-layout-column class="small">
+ </nb-layout-column>
+
+ <nb-layout-column right>
+ <ng-content select="router-outlet"></ng-content>
+ </nb-layout-column>
+
+ <nb-layout-footer fixed>
+ <ngx-footer></ngx-footer>
+ </nb-layout-footer>
+
+ </nb-layout>
+ `,
+})
+export class TwoColumnsLayoutComponent {
+}
diff --git a/webapp/src/app/@theme/layouts/xds/xds.layout.html b/webapp/src/app/@theme/layouts/xds/xds.layout.html
new file mode 100644
index 0000000..bf2b4e6
--- /dev/null
+++ b/webapp/src/app/@theme/layouts/xds/xds.layout.html
@@ -0,0 +1,46 @@
+<nb-layout [center]="layout.id === 'center-column'" windowMode>
+
+ <nb-layout-header fixed>
+ <ngx-header [position]="sidebar.id === 'left' ? 'normal': 'inverse'"></ngx-header>
+ </nb-layout-header>
+
+ <nb-sidebar class="menu-sidebar" tag="menu-sidebar" responsive [right]="sidebar.id === 'right'">
+
+ <nb-sidebar-header (click)="toogleSidebar()">
+ <!-- XXX - ugly rework -->
+ <nb-actions *ngIf="sidebar.id === 'left'" size="small" class="header-container right">
+ <nb-action *ngIf="!sidebarCompact" icon="fa fa-angle-double-left" style="margin-left: 80%;"></nb-action>
+ <nb-action *ngIf="sidebarCompact" icon="fa fa-angle-double-right"></nb-action>
+ </nb-actions>
+ <nb-actions *ngIf="sidebar.id === 'right'" size="small" class="header-container left">
+ <nb-action *ngIf="!sidebarCompact" icon="fa fa-angle-double-right" style="margin-right: 80%;"></nb-action>
+ <nb-action *ngIf="sidebarCompact" icon="fa fa-angle-double-left"></nb-action>
+ </nb-actions>
+ </nb-sidebar-header>
+
+ <ng-content select="nb-menu"></ng-content>
+
+ <nb-sidebar-footer>
+ </nb-sidebar-footer>
+ </nb-sidebar>
+
+ <nb-layout-column class="main-content">
+ <ng-content select="router-outlet"></ng-content>
+ </nb-layout-column>
+
+ <nb-layout-column left class="small" *ngIf="layout.id === 'two-column' || layout.id === 'three-column'">
+ <nb-menu [items]="subMenu"></nb-menu>
+ </nb-layout-column>
+
+ <nb-layout-column right class="small" *ngIf="layout.id === 'three-column'">
+ <nb-menu [items]="subMenu"></nb-menu>
+ </nb-layout-column>
+
+ <nb-layout-footer fixed>
+ <ngx-footer></ngx-footer>
+ </nb-layout-footer>
+
+ <nb-sidebar class="settings-sidebar" tag="settings-sidebar" state="collapsed" fixed [right]="sidebar.id !== 'right'">
+ <ngx-theme-settings></ngx-theme-settings>
+ </nb-sidebar>
+</nb-layout>
diff --git a/webapp/src/app/@theme/layouts/xds/xds.layout.scss b/webapp/src/app/@theme/layouts/xds/xds.layout.scss
new file mode 100644
index 0000000..7ccf7b7
--- /dev/null
+++ b/webapp/src/app/@theme/layouts/xds/xds.layout.scss
@@ -0,0 +1,130 @@
+@import '../../styles/themes';
+@import '~bootstrap/scss/mixins/breakpoints';
+@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
+
+@include nb-install-component() {
+ nb-layout-column.small {
+ flex: 0.15 !important;
+ }
+
+ nb-sidebar.settings-sidebar {
+ $sidebar-width: 7.5rem;
+
+ transition: width 0.3s ease;
+ width: $sidebar-width;
+ overflow: hidden;
+
+ &.collapsed {
+ width: 0;
+
+ /deep/ .main-container {
+ width: 0;
+
+ .scrollable {
+ width: $sidebar-width;
+ padding: 1.25rem;
+ }
+ }
+ }
+
+ /deep/ .main-container {
+ width: $sidebar-width;
+ background: nb-theme(color-bg);
+ transition: width 0.3s ease;
+ overflow: hidden;
+
+ .scrollable {
+ width: $sidebar-width;
+ }
+
+ @include nb-for-theme(cosmic) {
+ background: nb-theme(layout-bg);
+ }
+ }
+ }
+
+ nb-sidebar.menu-sidebar {
+
+ margin-top: nb-theme(sidebar-header-gap);
+
+ /deep/ .main-container {
+ height:
+ calc(#{nb-theme(sidebar-height)} - #{nb-theme(header-height)} - #{nb-theme(sidebar-header-gap)}) !important;
+ border-top-right-radius: nb-theme(radius);
+ }
+
+ /deep/ nb-sidebar-header {
+ padding-bottom: 0.5rem;
+ text-align: center;
+ }
+
+ background: transparent;
+
+ .main-btn {
+ padding: 0.75rem 2.5rem;
+ margin-top: -2rem;
+ font-weight: bold;
+ transition: padding 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.48);
+
+ i {
+ font-size: 2rem;
+ text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
+ }
+ span {
+ padding-left: 0.25rem;
+ }
+
+ i, span {
+ vertical-align: middle;
+ }
+ }
+
+ &.compacted {
+
+ /deep/ nb-sidebar-header {
+ padding-left: 0;
+ padding-right: 0;
+ }
+
+ .main-btn {
+ width: 46px;
+ height: 44px;
+ padding: 0.375rem;
+ border-radius: 5px;
+ transition: none;
+
+ span {
+ display: none;
+ }
+ }
+ }
+ }
+
+ @include media-breakpoint-down(xs) {
+ .main-content {
+ padding: 0.75rem !important;
+
+ }
+ }
+
+ @include media-breakpoint-down(sm) {
+
+ nb-sidebar.menu-sidebar {
+
+ margin-top: 0;
+
+ /deep/ .main-container {
+ height: calc(#{nb-theme(sidebar-height)} - #{nb-theme(header-height)}) !important;
+ border-top-right-radius: 0;
+
+ .scrollable {
+ padding-top: 0;
+ }
+ }
+ }
+
+ .main-btn {
+ display: none;
+ }
+ }
+}
diff --git a/webapp/src/app/@theme/layouts/xds/xds.layout.ts b/webapp/src/app/@theme/layouts/xds/xds.layout.ts
new file mode 100644
index 0000000..8987584
--- /dev/null
+++ b/webapp/src/app/@theme/layouts/xds/xds.layout.ts
@@ -0,0 +1,76 @@
+import { Component, OnDestroy } from '@angular/core';
+import {
+ NbMediaBreakpoint,
+ NbMediaBreakpointsService,
+ NbMenuItem,
+ NbMenuService,
+ NbSidebarService,
+ NbThemeService,
+} from '@nebular/theme';
+
+import { StateService } from '../../../@core/data/state.service';
+
+import { Subscription } from 'rxjs/Subscription';
+import 'rxjs/add/operator/withLatestFrom';
+import 'rxjs/add/operator/delay';
+
+// TODO: move layouts into the framework
+@Component({
+ selector: 'ngx-xds-layout',
+ styleUrls: ['./xds.layout.scss'],
+ templateUrl: './xds.layout.html',
+})
+
+export class XdsLayoutComponent implements OnDestroy {
+
+ subMenu: NbMenuItem[] = [];
+ layout: any = {};
+ sidebar: any = {};
+ sidebarCompact = true;
+
+ protected layoutState$: Subscription;
+ protected sidebarState$: Subscription;
+ protected menuClick$: Subscription;
+
+ constructor(protected stateService: StateService,
+ protected menuService: NbMenuService,
+ protected themeService: NbThemeService,
+ protected bpService: NbMediaBreakpointsService,
+ protected sidebarService: NbSidebarService) {
+ this.layoutState$ = this.stateService.onLayoutState()
+ .subscribe((layout: string) => this.layout = layout);
+
+ this.sidebarState$ = this.stateService.onSidebarState()
+ .subscribe((sidebar: string) => {
+ this.sidebar = sidebar;
+ });
+
+ const isBp = this.bpService.getByName('is');
+ this.menuClick$ = this.menuService.onItemSelect()
+ .withLatestFrom(this.themeService.onMediaQueryChange())
+ .delay(20)
+ .subscribe(([item, [bpFrom, bpTo]]: [any, [NbMediaBreakpoint, NbMediaBreakpoint]]) => {
+
+ this.sidebarCompact = false;
+ if (bpTo.width <= isBp.width) {
+ this.sidebarService.collapse('menu-sidebar');
+ }
+ });
+
+ // Set sidebarCompact according to sidebar state changes
+ this.sidebarService.onToggle().subscribe(s => s.tag === 'menu-sidebar' && (this.sidebarCompact = !this.sidebarCompact));
+ this.sidebarService.onCollapse().subscribe(s => s.tag === 'menu-sidebar' && (this.sidebarCompact = true));
+ this.sidebarService.onExpand().subscribe(() => this.sidebarCompact = false);
+ this.menuService.onSubmenuToggle().subscribe(i => i.item && i.item.expanded && (this.sidebarCompact = false));
+ }
+
+ toogleSidebar() {
+ this.sidebarService.toggle(true, 'menu-sidebar');
+ }
+
+ ngOnDestroy() {
+ this.layoutState$.unsubscribe();
+ this.sidebarState$.unsubscribe();
+ this.menuClick$.unsubscribe();
+ }
+}
diff --git a/webapp/src/app/@theme/pipes/.gitkeep b/webapp/src/app/@theme/pipes/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/webapp/src/app/@theme/pipes/.gitkeep
diff --git a/webapp/src/app/@theme/pipes/capitalize.pipe.ts b/webapp/src/app/@theme/pipes/capitalize.pipe.ts
new file mode 100644
index 0000000..61d5e58
--- /dev/null
+++ b/webapp/src/app/@theme/pipes/capitalize.pipe.ts
@@ -0,0 +1,11 @@
+import { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({ name: 'ngxCapitalize' })
+export class CapitalizePipe implements PipeTransform {
+
+ transform(input: string): string {
+ return input && input.length
+ ? (input.charAt(0).toUpperCase() + input.slice(1).toLowerCase())
+ : input;
+ }
+}
diff --git a/webapp/src/app/@theme/pipes/index.ts b/webapp/src/app/@theme/pipes/index.ts
new file mode 100644
index 0000000..541ebeb
--- /dev/null
+++ b/webapp/src/app/@theme/pipes/index.ts
@@ -0,0 +1,4 @@
+export * from './capitalize.pipe';
+export * from './plural.pipe';
+export * from './round.pipe';
+export * from './timing.pipe';
diff --git a/webapp/src/app/@theme/pipes/plural.pipe.ts b/webapp/src/app/@theme/pipes/plural.pipe.ts
new file mode 100644
index 0000000..4c34096
--- /dev/null
+++ b/webapp/src/app/@theme/pipes/plural.pipe.ts
@@ -0,0 +1,14 @@
+import { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({ name: 'ngxPlural' })
+export class PluralPipe implements PipeTransform {
+
+ transform(input: number, label: string, pluralLabel: string = ''): string {
+ input = input || 0;
+ return input === 1
+ ? `${input} ${label}`
+ : pluralLabel
+ ? `${input} ${pluralLabel}`
+ : `${input} ${label}s`;
+ }
+}
diff --git a/webapp/src/app/@theme/pipes/round.pipe.ts b/webapp/src/app/@theme/pipes/round.pipe.ts
new file mode 100644
index 0000000..3ec880f
--- /dev/null
+++ b/webapp/src/app/@theme/pipes/round.pipe.ts
@@ -0,0 +1,9 @@
+import { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({ name: 'ngxRound' })
+export class RoundPipe implements PipeTransform {
+
+ transform(input: number): number {
+ return Math.round(input);
+ }
+}
diff --git a/webapp/src/app/@theme/pipes/timing.pipe.ts b/webapp/src/app/@theme/pipes/timing.pipe.ts
new file mode 100644
index 0000000..afc9056
--- /dev/null
+++ b/webapp/src/app/@theme/pipes/timing.pipe.ts
@@ -0,0 +1,18 @@
+import { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({ name: 'timing' })
+export class TimingPipe implements PipeTransform {
+ transform(time: number): string {
+ if (time) {
+ const minutes = Math.floor(time / 60);
+ const seconds = Math.floor(time % 60);
+ return `${this.initZero(minutes)}${minutes}:${this.initZero(seconds)}${seconds}`;
+ }
+
+ return '00:00';
+ }
+
+ private initZero(time: number): string {
+ return time < 10 ? '0' : '';
+ }
+}
diff --git a/webapp/src/app/@theme/styles/pace.theme.scss b/webapp/src/app/@theme/styles/pace.theme.scss
new file mode 100644
index 0000000..e3bc9b6
--- /dev/null
+++ b/webapp/src/app/@theme/styles/pace.theme.scss
@@ -0,0 +1,22 @@
+/**
+ * @license
+ * Copyright Akveo. All Rights Reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ */
+
+ @mixin ngx-pace-theme() {
+
+ .pace .pace-progress {
+ background: nb-theme(color-fg-highlight);
+ }
+
+ .pace .pace-progress-inner {
+ box-shadow: 0 0 10px nb-theme(color-fg-highlight), 0 0 5px nb-theme(color-fg-highlight);
+ }
+
+ .pace .pace-activity {
+ display: none;
+ // border-top-color: nb-theme(color-fg-highlight);
+ // border-left-color: nb-theme(color-fg-highlight);
+ }
+}
diff --git a/webapp/src/app/@theme/styles/styles.scss b/webapp/src/app/@theme/styles/styles.scss
new file mode 100644
index 0000000..a943e7a
--- /dev/null
+++ b/webapp/src/app/@theme/styles/styles.scss
@@ -0,0 +1,20 @@
+// themes - our custom or/and out of the box themes
+@import 'themes';
+
+// framework component themes (styles tied to theme variables)
+@import '~@nebular/theme/styles/globals';
+@import '~@nebular/auth/styles/all';
+
+// loading progress bar theme
+@import './pace.theme';
+
+// install the framework and custom global styles
+@include nb-install() {
+
+ // framework global styles
+ @include nb-theme-global();
+ @include nb-auth-global();
+
+ // loading progress bar
+ @include ngx-pace-theme();
+};
diff --git a/webapp/src/app/@theme/styles/theme.cosmic.ts b/webapp/src/app/@theme/styles/theme.cosmic.ts
new file mode 100644
index 0000000..a252beb
--- /dev/null
+++ b/webapp/src/app/@theme/styles/theme.cosmic.ts
@@ -0,0 +1,87 @@
+export const COSMIC_THEME = {
+ name: 'cosmic',
+ base: 'default',
+ variables: {
+
+ temperature: [
+ '#2ec7fe',
+ '#31ffad',
+ '#7bff24',
+ '#fff024',
+ '#f7bd59',
+ ],
+
+ solar: {
+ gradientLeft: '#7bff24',
+ gradientRight: '#2ec7fe',
+ shadowColor: '#19977E',
+ radius: ['70%', '90%'],
+ },
+
+ traffic: {
+ colorBlack: '#000000',
+ tooltipBg: 'rgba(0, 255, 170, 0.35)',
+ tooltipBorderColor: '#00d977',
+ tooltipExtraCss: 'box-shadow: 0px 2px 46px 0 rgba(0, 255, 170, 0.35); border-radius: 10px; padding: 4px 16px;',
+ tooltipTextColor: '#ffffff',
+ tooltipFontWeight: 'normal',
+
+ lineBg: '#d1d1ff',
+ lineShadowBlur: '14',
+ itemColor: '#BEBBFF',
+ itemBorderColor: '#ffffff',
+ itemEmphasisBorderColor: '#ffffff',
+ shadowLineDarkBg: '#655ABD',
+ shadowLineShadow: 'rgba(33, 7, 77, 0.5)',
+ gradFrom: 'rgba(118, 89, 255, 0.4)',
+ gradTo: 'rgba(164, 84, 255, 0.5)',
+ },
+
+ electricity: {
+ tooltipBg: 'rgba(0, 255, 170, 0.35)',
+ tooltipLineColor: 'rgba(255, 255, 255, 0.1)',
+ tooltipLineWidth: '1',
+ tooltipBorderColor: '#00d977',
+ tooltipExtraCss: 'box-shadow: 0px 2px 46px 0 rgba(0, 255, 170, 0.35); border-radius: 10px; padding: 8px 24px;',
+ tooltipTextColor: '#ffffff',
+ tooltipFontWeight: 'normal',
+
+ axisLineColor: 'rgba(161, 161 ,229, 0.3)',
+ xAxisTextColor: '#a1a1e5',
+ yAxisSplitLine: 'rgba(161, 161 ,229, 0.2)',
+
+ itemBorderColor: '#ffffff',
+ lineStyle: 'dotted',
+ lineWidth: '6',
+ lineGradFrom: '#00ffaa',
+ lineGradTo: '#fff835',
+ lineShadow: 'rgba(14, 16, 48, 0.4)',
+
+ areaGradFrom: 'rgba(188, 92, 255, 0.5)',
+ areaGradTo: 'rgba(188, 92, 255, 0)',
+ shadowLineDarkBg: '#a695ff',
+ },
+
+ bubbleMap: {
+ titleColor: '#ffffff',
+ areaColor: '#2c2961',
+ areaHoverColor: '#a1a1e5',
+ areaBorderColor: '#654ddb',
+ },
+
+ echarts: {
+ bg: '#3d3780',
+ textColor: '#ffffff',
+ axisLineColor: '#a1a1e5',
+ splitLineColor: '#342e73',
+ itemHoverShadowColor: 'rgba(0, 0, 0, 0.5)',
+ tooltipBackgroundColor: '#6a7985',
+ areaOpacity: '1',
+ },
+
+ chartjs: {
+ axisLineColor: '#a1a1e5',
+ textColor: '#ffffff',
+ },
+ },
+};
diff --git a/webapp/src/app/@theme/styles/theme.default.ts b/webapp/src/app/@theme/styles/theme.default.ts
new file mode 100644
index 0000000..d387c57
--- /dev/null
+++ b/webapp/src/app/@theme/styles/theme.default.ts
@@ -0,0 +1,88 @@
+export const DEFAULT_THEME = {
+ name: 'default',
+ base: null,
+ variables: {
+
+ // Safari fix
+ temperature: [
+ '#42db7d',
+ '#42db7d',
+ '#42db7d',
+ '#42db7d',
+ '#42db7d',
+ ],
+
+ solar: {
+ gradientLeft: '#42db7d',
+ gradientRight: '#42db7d',
+ shadowColor: 'rgba(0, 0, 0, 0)',
+ radius: ['80%', '90%'],
+ },
+
+ traffic: {
+ colorBlack: '#000000',
+ tooltipBg: '#ffffff',
+ tooltipBorderColor: '#c0c8d1',
+ tooltipExtraCss: 'border-radius: 10px; padding: 4px 16px;',
+ tooltipTextColor: '#2a2a2a',
+ tooltipFontWeight: 'bolder',
+
+ lineBg: '#c0c8d1',
+ lineShadowBlur: '1',
+ itemColor: '#bcc3cc',
+ itemBorderColor: '#bcc3cc',
+ itemEmphasisBorderColor: '#42db7d',
+ shadowLineDarkBg: 'rgba(0, 0, 0, 0)',
+ shadowLineShadow: 'rgba(0, 0, 0, 0)',
+ gradFrom: '#ebeef2',
+ gradTo: '#ebeef2',
+ },
+
+ electricity: {
+ tooltipBg: '#ffffff',
+ tooltipLineColor: 'rgba(0, 0, 0, 0)',
+ tooltipLineWidth: '0',
+ tooltipBorderColor: '#ebeef2',
+ tooltipExtraCss: 'border-radius: 10px; padding: 8px 24px;',
+ tooltipTextColor: '#2a2a2a',
+ tooltipFontWeight: 'bolder',
+
+ axisLineColor: 'rgba(0, 0, 0, 0)',
+ xAxisTextColor: '#2a2a2a',
+ yAxisSplitLine: '#ebeef2',
+
+ itemBorderColor: '#42db7d',
+ lineStyle: 'solid',
+ lineWidth: '4',
+ lineGradFrom: '#42db7d',
+ lineGradTo: '#42db7d',
+ lineShadow: 'rgba(0, 0, 0, 0)',
+
+ areaGradFrom: 'rgba(235, 238, 242, 0.5)',
+ areaGradTo: 'rgba(235, 238, 242, 0.5)',
+ shadowLineDarkBg: 'rgba(0, 0, 0, 0)',
+ },
+
+ bubbleMap: {
+ titleColor: '#484848',
+ areaColor: '#dddddd',
+ areaHoverColor: '#cccccc',
+ areaBorderColor: '#ebeef2',
+ },
+
+ echarts: {
+ bg: '#ffffff',
+ textColor: '#484848',
+ axisLineColor: '#bbbbbb',
+ splitLineColor: '#ebeef2',
+ itemHoverShadowColor: 'rgba(0, 0, 0, 0.5)',
+ tooltipBackgroundColor: '#6a7985',
+ areaOpacity: '0.7',
+ },
+
+ chartjs: {
+ axisLineColor: '#cccccc',
+ textColor: '#484848',
+ },
+ },
+};
diff --git a/webapp/src/app/@theme/styles/themes.scss b/webapp/src/app/@theme/styles/themes.scss
new file mode 100644
index 0000000..f5f75b6
--- /dev/null
+++ b/webapp/src/app/@theme/styles/themes.scss
@@ -0,0 +1,27 @@
+// @nebular theming framework
+@import '~@nebular/theme/styles/theming';
+// @nebular out of the box themes
+@import '~@nebular/theme/styles/themes';
+
+// which themes you what to enable (empty to enable all)
+$nb-enabled-themes: (default, cosmic);
+
+$nb-themes: nb-register-theme((
+ // app wise variables for each theme
+ sidebar-header-gap: 2rem,
+ sidebar-header-height: initial,
+ layout-content-width: 1400px,
+
+ font-main: Roboto,
+ font-secondary: Exo,
+), default, default);
+
+$nb-themes: nb-register-theme((
+ // app wise variables for each theme
+ sidebar-header-gap: 2rem,
+ sidebar-header-height: initial,
+ layout-content-width: 1400px,
+
+ font-main: Roboto,
+ font-secondary: Exo,
+), cosmic, cosmic);
diff --git a/webapp/src/app/@theme/theme.module.ts b/webapp/src/app/@theme/theme.module.ts
new file mode 100644
index 0000000..d786226
--- /dev/null
+++ b/webapp/src/app/@theme/theme.module.ts
@@ -0,0 +1,99 @@
+import { ModuleWithProviders, NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
+
+import {
+ NbActionsModule,
+ NbCardModule,
+ NbLayoutModule,
+ NbMenuModule,
+ NbRouteTabsetModule,
+ NbSearchModule,
+ NbSidebarModule,
+ NbTabsetModule,
+ NbThemeModule,
+ NbUserModule,
+ NbCheckboxModule,
+} from '@nebular/theme';
+
+import {
+ FooterComponent,
+ HeaderComponent,
+ SearchInputComponent,
+ ThemeSettingsComponent,
+ ThemeSwitcherComponent,
+ // XDS_MODS TinyMCEComponent,
+} from './components';
+import { CapitalizePipe, PluralPipe, RoundPipe, TimingPipe } from './pipes';
+import {
+ OneColumnLayoutComponent,
+ SampleLayoutComponent,
+ XdsLayoutComponent, // XDS_MODS
+ ThreeColumnsLayoutComponent,
+ TwoColumnsLayoutComponent,
+} from './layouts';
+import { DEFAULT_THEME } from './styles/theme.default';
+import { COSMIC_THEME } from './styles/theme.cosmic';
+
+const BASE_MODULES = [CommonModule, FormsModule, ReactiveFormsModule];
+
+const NB_MODULES = [
+ NbCardModule,
+ NbLayoutModule,
+ NbTabsetModule,
+ NbRouteTabsetModule,
+ NbMenuModule,
+ NbUserModule,
+ NbActionsModule,
+ NbSearchModule,
+ NbSidebarModule,
+ NbCheckboxModule,
+ NgbModule,
+];
+
+const COMPONENTS = [
+ ThemeSwitcherComponent,
+ HeaderComponent,
+ FooterComponent,
+ SearchInputComponent,
+ ThemeSettingsComponent,
+ // XDS_MODS TinyMCEComponent,
+ OneColumnLayoutComponent,
+ SampleLayoutComponent,
+ XdsLayoutComponent, // XDS_MODS
+ ThreeColumnsLayoutComponent,
+ TwoColumnsLayoutComponent,
+];
+
+const PIPES = [
+ CapitalizePipe,
+ PluralPipe,
+ RoundPipe,
+ TimingPipe,
+];
+
+const NB_THEME_PROVIDERS = [
+ ...NbThemeModule.forRoot(
+ {
+ name: 'default', // XDS_MODS
+ },
+ [ DEFAULT_THEME, COSMIC_THEME ],
+ ).providers,
+ ...NbSidebarModule.forRoot().providers,
+ ...NbMenuModule.forRoot().providers,
+];
+
+@NgModule({
+ imports: [...BASE_MODULES, ...NB_MODULES],
+ exports: [...BASE_MODULES, ...NB_MODULES, ...COMPONENTS, ...PIPES],
+ declarations: [...COMPONENTS, ...PIPES],
+})
+export class ThemeModule {
+ static forRoot(): ModuleWithProviders {
+ return <ModuleWithProviders>{
+ ngModule: ThemeModule,
+ providers: [...NB_THEME_PROVIDERS],
+ };
+ }
+}
diff --git a/webapp/src/app/app-alert/app-alert.component.spec.ts b/webapp/src/app/app-alert/app-alert.component.spec.ts
deleted file mode 100644
index 7f343dc..0000000
--- a/webapp/src/app/app-alert/app-alert.component.spec.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { AppAlertComponent } from './app-alert.component';
-
-describe('AppAlertComponent', () => {
- let component: AppAlertComponent;
- let fixture: ComponentFixture<AppAlertComponent>;
-
- beforeEach(async(() => {
- TestBed.configureTestingModule({
- declarations: [ AppAlertComponent ]
- })
- .compileComponents();
- }));
-
- beforeEach(() => {
- fixture = TestBed.createComponent(AppAlertComponent);
- component = fixture.componentInstance;
- fixture.detectChanges();
- });
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });
-});
diff --git a/webapp/src/app/app-alert/app-alert.component.ts b/webapp/src/app/app-alert/app-alert.component.ts
deleted file mode 100644
index e6fbd47..0000000
--- a/webapp/src/app/app-alert/app-alert.component.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { Component, ViewEncapsulation } from '@angular/core';
-import { Observable } from 'rxjs/Observable';
-
-import { AlertService, IAlert } from '../services/alert.service';
-
-@Component({
- selector: 'app-alert',
- template: `
- <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)">
- <div style="text-align:center;" [innerHtml]="alert.msg"></div>
- </alert>
- </div>
-`,
-})
-
-export class AppAlertComponent {
-
- alerts$: Observable<IAlert[]>;
-
- constructor(private alertSvr: AlertService) {
- this.alerts$ = this.alertSvr.alerts;
- }
-
- onClose(al) {
- this.alertSvr.del(al);
- }
-
-}
diff --git a/webapp/src/app/app-routing.module.ts b/webapp/src/app/app-routing.module.ts
index 36629de..5490a21 100644
--- a/webapp/src/app/app-routing.module.ts
+++ b/webapp/src/app/app-routing.module.ts
@@ -1,18 +1,57 @@
+import { ExtraOptions, RouterModule, Routes } from '@angular/router';
import { NgModule } from '@angular/core';
-import { Routes, RouterModule } from '@angular/router';
-import { HomeComponent } from './home/home.component';
-import { ConfigComponent } from './config/config.component';
-import { DevelComponent } from './devel/devel.component';
+import {
+ NbAuthComponent,
+ NbLoginComponent,
+ NbLogoutComponent,
+ NbRegisterComponent,
+ NbRequestPasswordComponent,
+ NbResetPasswordComponent,
+} from '@nebular/auth';
const routes: Routes = [
- { path: 'config', component: ConfigComponent, data: { title: 'Config' } },
- { path: 'home', component: HomeComponent, data: { title: 'Home' } },
- { path: 'devel', component: DevelComponent, data: { title: 'Build & Deploy' } },
- { path: '**', component: HomeComponent }
+ { path: 'pages', loadChildren: 'app/pages/pages.module#PagesModule' },
+ {
+ path: 'auth',
+ component: NbAuthComponent,
+ children: [
+ {
+ path: '',
+ component: NbLoginComponent,
+ },
+ {
+ path: 'login',
+ component: NbLoginComponent,
+ },
+ {
+ path: 'register',
+ component: NbRegisterComponent,
+ },
+ {
+ path: 'logout',
+ component: NbLogoutComponent,
+ },
+ {
+ path: 'request-password',
+ component: NbRequestPasswordComponent,
+ },
+ {
+ path: 'reset-password',
+ component: NbResetPasswordComponent,
+ },
+ ],
+ },
+ { path: '', redirectTo: 'pages', pathMatch: 'full' },
+ { path: '**', redirectTo: 'pages' },
];
+const config: ExtraOptions = {
+ useHash: true,
+};
+
@NgModule({
- imports: [RouterModule.forRoot(routes)],
- exports: [RouterModule]
+ imports: [RouterModule.forRoot(routes, config)],
+ exports: [RouterModule],
})
-export class AppRoutingModule { }
+export class AppRoutingModule {
+}
diff --git a/webapp/src/app/app-topnav/app-topnav.component.css b/webapp/src/app/app-topnav/app-topnav.component.css
deleted file mode 100644
index a47ad13..0000000
--- a/webapp/src/app/app-topnav/app-topnav.component.css
+++ /dev/null
@@ -1,31 +0,0 @@
-.navbar {
- background-color: whitesmoke;
-}
-
-.navbar-brand {
- font-size: x-large;
- font-variant: small-caps;
- color: #5a28a1;
-}
-
-a.navbar-brand {
- margin-top: 5px;
-}
-
-
-.navbar-nav ul li a {
- color: #fff;
-}
-
-.menu-text {
- color: #fff;
-}
-
-#logo-iot {
- padding: 0 2px;
- height: 60px;
-}
-
-li>a {
- color:#5a28a1;
-}
diff --git a/webapp/src/app/app-topnav/app-topnav.component.html b/webapp/src/app/app-topnav/app-topnav.component.html
deleted file mode 100644
index be2dfa2..0000000
--- a/webapp/src/app/app-topnav/app-topnav.component.html
+++ /dev/null
@@ -1,24 +0,0 @@
-<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"
- [attr.aria-expanded]="!isCollapsed" (click)="isCollapsed = !isCollapsed;" [ngClass]="{'collapsed': isCollapsed}">
- <span class="icon-bar"></span>
- <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>
-
- <div class="collapse navbar-collapse" [ngClass]="{'in': !isCollapsed}" id="myNavbar">
- <ul class="nav navbar-nav navbar-right">
- <li><a routerLink="/config"><i class="fa fa-2x fa-cog" title="Open configuration page" (click)="isCollapsed=true;"></i></a></li>
- <li><a routerLink="/devel"><i class="fa fa-2x fa-play-circle" title="Open build page" (click)="isCollapsed=true;"></i></a></li>
- <li><a routerLink="/home"><i class="fa fa-2x fa-home" title="Back to home page" (click)="isCollapsed=true;"></i></a></li>
- </ul>
- </div>
- </div>
-</nav>
diff --git a/webapp/src/app/app-topnav/app-topnav.component.ts b/webapp/src/app/app-topnav/app-topnav.component.ts
deleted file mode 100644
index 9ba4021..0000000
--- a/webapp/src/app/app-topnav/app-topnav.component.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Component, ViewEncapsulation } from '@angular/core';
-
-@Component({
- selector: 'app-topnav',
- templateUrl: './app-topnav.component.html',
- styleUrls: ['./app-topnav.component.css'],
- encapsulation: ViewEncapsulation.None
-})
-export class AppTopnavComponent {
- public isCollapsed = false;
-
- constructor() { }
-}
diff --git a/webapp/src/app/app.component.html b/webapp/src/app/app.component.html
deleted file mode 100644
index 6fcb46f..0000000
--- a/webapp/src/app/app.component.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<app-topnav></app-topnav>
-
-<app-alert id="alert"></app-alert>
-
-<div style="margin:10px;">
- <router-outlet></router-outlet>
-</div>
diff --git a/webapp/src/app/app.component.ts b/webapp/src/app/app.component.ts
index b644ecd..f858b0c 100644
--- a/webapp/src/app/app.component.ts
+++ b/webapp/src/app/app.component.ts
@@ -1,44 +1,21 @@
-import { Component, OnInit, OnDestroy } from '@angular/core';
-import { TranslateService } from '@ngx-translate/core';
-import { ConfigService, IConfig } from './services/config.service';
+/**
+ * @license
+ * Copyright Akveo. All Rights Reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ */
+import { Component, OnInit } from '@angular/core';
+import { AnalyticsService } from './@core/utils/analytics.service';
@Component({
- selector: 'app-root',
- templateUrl: 'app.component.html'
+ selector: 'ngx-app',
+ template: '<router-outlet></router-outlet>',
})
+export class AppComponent implements OnInit {
-export class AppComponent implements OnInit, OnDestroy {
- private defaultLanguage = 'en';
- public isCollapsed = true;
+ constructor(private analytics: AnalyticsService) {
+ }
- constructor(private translate: TranslateService, private configSvr: ConfigService) {
- }
-
- ngOnInit() {
- this.translate.addLangs(['en', 'fr']);
- this.translate.setDefaultLang(this.defaultLanguage);
-
- const browserLang = this.translate.getBrowserLang();
- this.translate.use(browserLang.match(/en|fr/) ? browserLang : this.defaultLanguage);
-
- this.configSvr.Conf$.subscribe((cfg: IConfig) => {
- let lang: string;
- switch (cfg.language) {
- case 'ENG':
- lang = 'en';
- break;
- case 'FRA':
- lang = 'fr';
- break;
- default:
- lang = this.defaultLanguage;
- }
- this.translate.use(lang);
- });
- }
-
- ngOnDestroy(): void {
- // this.aglIdentityService.loginResponse.unsubscribe();
- // this.aglIdentityService.logoutResponse.unsubscribe();
- }
+ ngOnInit(): void {
+ this.analytics.trackPageViews();
+ }
}
diff --git a/webapp/src/app/app.module.ts b/webapp/src/app/app.module.ts
index bf63b5e..9d992e7 100644
--- a/webapp/src/app/app.module.ts
+++ b/webapp/src/app/app.module.ts
@@ -1,110 +1,36 @@
-import { NgModule } from '@angular/core';
-import { HttpClientModule, HttpClient } from '@angular/common/http';
+/**
+ * @license
+ * Copyright Akveo. All Rights Reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ */
+import { APP_BASE_HREF } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
-import { FormsModule, ReactiveFormsModule } from '@angular/forms';
-import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
-import { TranslateHttpLoader } from '@ngx-translate/http-loader';
-import { FileUploadModule } from 'ng2-file-upload';
-import { LocationStrategy, HashLocationStrategy } from '@angular/common';
-import { CookieModule } from 'ngx-cookie';
-
-// Import bootstrap
-import { AlertModule } from 'ngx-bootstrap/alert';
-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 { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { NgModule } from '@angular/core';
+import { HttpModule } from '@angular/http';
+import { CoreXdsModule } from './@core-xds/core-xds.module';
-// Import the application components and services.
-import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
-import { AppTopnavComponent } from './app-topnav/app-topnav.component';
-import { AppAlertComponent } from './app-alert/app-alert.component';
-import { HomeComponent } from './home/home.component';
-import { ConfigComponent } from './config/config.component';
-import { DwnlAgentComponent } from './config/downloadXdsAgent.component';
-import { DevelComponent } from './devel/devel.component';
-import { BuildComponent } from './devel/build/build.component';
-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 { AlertService } from './services/alert.service';
-import { ConfigService } from './services/config.service';
-import { ProjectService } from './services/project.service';
-import { SdkService } from './services/sdk.service';
-import { UtilsService } from './services/utils.service';
-import { XDSAgentService } from './services/xdsagent.service';
-
-import { SafePipe } from './common/safe.pipe';
-
-export function createTranslateLoader(http: HttpClient) {
- return new TranslateHttpLoader(http, './assets/i18n/', '.json');
-}
+import { AppRoutingModule } from './app-routing.module';
+import { ThemeModule } from './@theme/theme.module';
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
@NgModule({
- imports: [
- BrowserModule,
- FormsModule,
- ReactiveFormsModule,
- HttpClientModule,
- AppRoutingModule,
- FileUploadModule,
- TranslateModule.forRoot({
- loader: {
- provide: TranslateLoader,
- useFactory: (createTranslateLoader),
- deps: [HttpClient]
- }
- }),
- CookieModule.forRoot(),
- AlertModule.forRoot(),
- ModalModule.forRoot(),
- AccordionModule.forRoot(),
- CarouselModule.forRoot(),
- PopoverModule.forRoot(),
- CollapseModule.forRoot(),
- BsDropdownModule.forRoot(),
- ],
- declarations: [
- AppComponent,
- AppTopnavComponent,
- AppAlertComponent,
- HomeComponent,
- ConfigComponent,
- DwnlAgentComponent,
- DevelComponent,
- BuildComponent,
- ProjectCardComponent,
- ProjectReadableTypePipe,
- ProjectsListAccordionComponent,
- ProjectAddModalComponent,
- SdkCardComponent,
- SdksListAccordionComponent,
- SdkSelectDropdownComponent,
- SdkAddModalComponent,
- SafePipe,
- ],
- providers: [
- {
- provide: LocationStrategy, useClass: HashLocationStrategy,
- },
- AlertService,
- ConfigService,
- ProjectService,
- SdkService,
- UtilsService,
- XDSAgentService
- ],
- bootstrap: [AppComponent]
+ declarations: [AppComponent],
+ imports: [
+ BrowserModule,
+ BrowserAnimationsModule,
+ HttpModule,
+ AppRoutingModule,
+
+ NgbModule.forRoot(),
+ ThemeModule.forRoot(),
+ CoreXdsModule.forRoot(),
+ ],
+ bootstrap: [AppComponent],
+ providers: [
+ { provide: APP_BASE_HREF, useValue: '/' },
+ ],
})
export class AppModule {
- constructor() { }
}
diff --git a/webapp/src/app/common/safe.pipe.ts b/webapp/src/app/common/safe.pipe.ts
deleted file mode 100644
index 84fd6b5..0000000
--- a/webapp/src/app/common/safe.pipe.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { Pipe, PipeTransform } from '@angular/core';
-import { DomSanitizer } from '@angular/platform-browser';
-
-@Pipe({ name: 'safe' })
-export class SafePipe implements PipeTransform {
- constructor(private sanitizer: DomSanitizer) { }
- transform(url) {
- return this.sanitizer.bypassSecurityTrustResourceUrl(url);
- }
-}
diff --git a/webapp/src/app/config/config.component.css b/webapp/src/app/config/config.component.css
deleted file mode 100644
index 6412f9a..0000000
--- a/webapp/src/app/config/config.component.css
+++ /dev/null
@@ -1,35 +0,0 @@
-.fa-big {
- font-size: 20px;
- font-weight: bold;
-}
-
-.fa-size-x2 {
- font-size: 20px;
-}
-
-h2 {
- font-family: sans-serif;
- font-variant: small-caps;
- font-size: x-large;
-}
-
-th span {
- font-weight: 100;
-}
-
-th label {
- font-weight: 100;
- margin-bottom: 0;
-}
-
-tr.info>th {
- vertical-align: middle;
-}
-
-tr.info>td {
- vertical-align: middle;
-}
-
-.panel-heading {
- background: aliceblue;
-}
diff --git a/webapp/src/app/config/config.component.html b/webapp/src/app/config/config.component.html
deleted file mode 100644
index ba3bd72..0000000
--- a/webapp/src/app/config/config.component.html
+++ /dev/null
@@ -1,101 +0,0 @@
-<div class="panel panel-default">
- <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]="((agentStatus$ | 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" [collapse]="gConfigIsCollapsed && xdsServerConnected">
- <div class="row">
- <div class="col-xs-12">
- <table class="table table-condensed">
- <tbody>
- <tr [ngClass]="{'info': xdsServerConnected, 'danger': !xdsServerConnected}">
- <th><label>XDS Server URL</label></th>
- <td> <input type="text" [(ngModel)]="xdsServerUrl"></td>
- <td style="white-space: nowrap">
- <div class="btn-group">
- <button class="btn btn-link" (click)="xdsAgentRestartConn()"><span class="fa fa-refresh fa-size-x2"></span></button>
- <xds-dwnl-agent class="button"></xds-dwnl-agent>
- </div>
- </td>
- </tr>
- <tr class="info">
- <th><label>XDS Server connection retry</label></th>
- <td> <input type="text" [(ngModel)]="xdsServerRetry" (ngModelChange)="showApplyBtn['retry'] = true"></td>
- <td>
- <button *ngIf="showApplyBtn['retry']" class="btn btn-primary btn-xs" (click)="submitGlobConf('retry')">APPLY</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>
- <td>
- <button *ngIf="showApplyBtn['rootDir']" class="btn btn-primary btn-xs" (click)="submitGlobConf('rootDir')">APPLY</button>
- </td>
- </tr>
- </tbody>
- </table>
- </div>
- </div>
- </div>
-</div>
-
-<div class="panel panel-default">
- <div class="panel-heading">
- <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" [collapse]="sdksIsCollapsed">
- <div class="row col-xs-12">
- <xds-sdks-list-accordion [sdks]="(sdks$ | async)"></xds-sdks-list-accordion>
- </div>
- </div>
-</div>
-
-<div class="panel panel-default">
- <div class="panel-heading">
- <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>
-
- <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>
- </h2>
- </div>
- <div class="panel-body" [collapse]="projectsIsCollapsed">
- <div class="row col-xs-12">
- <xds-projects-list-accordion [projects]="(projects$ | async)"></xds-projects-list-accordion>
- </div>
- </div>
-</div>
-
-<!-- Modals -->
-<xds-project-add-modal #childProjectModal [title]="'Add a new project'" [server-id]=curServerID>
-</xds-project-add-modal>
-<xds-sdk-add-modal #childSdkModal [title]="'Add a new SDK'">
-</xds-sdk-add-modal>
-
-<!-- only for debug -->
-<div *ngIf="false" class="row">
- <pre>Config: {{config$ | async | json}}</pre>
- <br>
- <pre>Projects: {{projects$ | async | json}} </pre>
-</div>
diff --git a/webapp/src/app/config/config.component.spec.ts b/webapp/src/app/config/config.component.spec.ts
deleted file mode 100644
index ec5d3be..0000000
--- a/webapp/src/app/config/config.component.spec.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { ConfigComponent } from './config.component';
-
-describe('ConfigComponent', () => {
- let component: ConfigComponent;
- let fixture: ComponentFixture<ConfigComponent>;
-
- beforeEach(async(() => {
- TestBed.configureTestingModule({
- declarations: [ ConfigComponent ]
- })
- .compileComponents();
- }));
-
- beforeEach(() => {
- fixture = TestBed.createComponent(ConfigComponent);
- component = fixture.componentInstance;
- fixture.detectChanges();
- });
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });
-});
diff --git a/webapp/src/app/config/config.component.ts b/webapp/src/app/config/config.component.ts
deleted file mode 100644
index 3db7f60..0000000
--- a/webapp/src/app/config/config.component.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
-import { Observable } from 'rxjs/Observable';
-import { CollapseModule } from 'ngx-bootstrap/collapse';
-
-import { ConfigService, IConfig } from '../services/config.service';
-import { ProjectService, IProject } from '../services/project.service';
-import { XDSAgentService, IAgentStatus, IXDSConfig } from '../services/xdsagent.service';
-import { AlertService } from '../services/alert.service';
-import { ProjectAddModalComponent } from '../projects/projectAddModal.component';
-import { SdkService, ISdk } from '../services/sdk.service';
-import { SdkAddModalComponent } from '../sdks/sdkAddModal.component';
-
-@Component({
- selector: 'app-config',
- templateUrl: './config.component.html',
- styleUrls: ['./config.component.css'],
- encapsulation: ViewEncapsulation.None
-})
-
-// Inspired from https://embed.plnkr.co/jgDTXknPzAaqcg9XA9zq/
-// 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>;
- projects$: Observable<IProject[]>;
- sdks$: Observable<ISdk[]>;
- agentStatus$: Observable<IAgentStatus>;
-
- curProj: number;
- curServer: number;
- curServerID: string;
- userEditedLabel = false;
-
- gConfigIsCollapsed = true;
- sdksIsCollapsed = true;
- projectsIsCollapsed = false;
-
- // TODO replace by reactive FormControl + add validation
- xdsServerConnected = false;
- xdsServerUrl: string;
- xdsServerRetry: 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,
- };
-
- constructor(
- private configSvr: ConfigService,
- private projectSvr: ProjectService,
- private xdsAgentSvr: XDSAgentService,
- private sdkSvr: SdkService,
- private alert: AlertService,
- ) {
- }
-
- ngOnInit() {
- this.config$ = this.configSvr.Conf$;
- this.projects$ = this.projectSvr.Projects$;
- this.sdks$ = this.sdkSvr.Sdks$;
- this.agentStatus$ = this.xdsAgentSvr.Status$;
-
- // FIXME support multiple servers
- this.curServer = 0;
-
- // Bind xdsServerUrl to baseURL
- this.xdsAgentSvr.XdsConfig$.subscribe(cfg => {
- if (!cfg || cfg.servers.length < 1) {
- return;
- }
- const svr = cfg.servers[this.curServer];
- this.curServerID = svr.id;
- this.xdsServerConnected = svr.connected;
- this.xdsServerUrl = svr.url;
- this.xdsServerRetry = String(svr.connRetry);
- this.projectsRootDir = ''; // SEB FIXME: add in go config? cfg.projectsRootDir;
- });
- }
-
- submitGlobConf(field: string) {
- switch (field) {
- case 'retry':
- const re = new RegExp('^[0-9]+$');
- const rr = parseInt(this.xdsServerRetry, 10);
- if (re.test(this.xdsServerRetry) && rr >= 0) {
- this.xdsAgentSvr.setServerRetry(this.curServerID, rr);
- } else {
- this.alert.warning('Not a valid number', true);
- }
- break;
- case 'rootDir':
- this.configSvr.projectsRootDir = this.projectsRootDir;
- break;
- default:
- return;
- }
- this.showApplyBtn[field] = false;
- }
-
- xdsAgentRestartConn() {
- const url = this.xdsServerUrl;
- this.xdsAgentSvr.setServerUrl(this.curServerID, url);
- }
-
-}
diff --git a/webapp/src/app/devel/build/build.component.css b/webapp/src/app/devel/build/build.component.css
deleted file mode 100644
index 695a89b..0000000
--- a/webapp/src/app/devel/build/build.component.css
+++ /dev/null
@@ -1,54 +0,0 @@
-.vcenter {
- display: inline-block;
- vertical-align: middle;
-}
-
-.blocks .btn-primary {
- margin-left: 5px;
- margin-right: 5px;
- margin-top: 5px;
- border-radius: 4px !important;
-}
-
-.table-center {
- width: 80%;
- margin-left: auto;
- margin-right: auto;
-}
-
-.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;
-}
-
-.table-in-accordion>tbody>tr>th {
- width: 30%
-}
-
-.btn-large {
- width: 10em;
-}
-
-.fa-big {
- font-size: 18px;
- font-weight: bold;
-}
-
-.textarea-scroll {
- width: 100%;
- overflow-y: scroll;
-}
-
-h2 {
- font-family: sans-serif;
- font-variant: small-caps;
- font-size: x-large;
-}
-
-.panel-heading {
- background: aliceblue;
-}
diff --git a/webapp/src/app/devel/build/build.component.html b/webapp/src/app/devel/build/build.component.html
deleted file mode 100644
index 0cf0290..0000000
--- a/webapp/src/app/devel/build/build.component.html
+++ /dev/null
@@ -1,114 +0,0 @@
-<div class="panel panel-default">
- <div class="panel-heading">
- <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" [collapse]="buildIsCollapsed">
- <form [formGroup]="buildForm">
- <div class="col-xs-12">
- <table class="table table-borderless table-center">
- <tbody>
- <tr>
- <th>Cross SDK</th>
- <td>
- <!-- FIXME why not working ?
- <xds-sdk-select-dropdown [sdks]="(sdks$ | async)"></xds-sdk-select-dropdown>
- -->
- <xds-sdk-select-dropdown></xds-sdk-select-dropdown>
- </td>
- </tr>
- <tr>
- <th>Project root path</th>
- <td> <input type="text" disabled style="width:99%;" [value]="curProject && curProject.pathClient"></td>
- </tr>
- <tr>
- <th>Sub-path</th>
- <td> <input type="text" style="width:99%;" formControlName="subpath"> </td>
- </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>
-
- <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>
- </table>
- </div>
- <div class="row">
- <div class="col-xs-12 text-center">
- <div class="btn-group blocks">
- <button class="btn btn-primary btn-large" (click)="clean()" [disabled]="!curProject ">Clean</button>
- <button class="btn btn-primary btn-large" (click)="preBuild()" [disabled]="!curProject">Pre-Build</button>
- <button class="btn btn-primary btn-large" (click)="build()" [disabled]="!curProject">Build</button>
- <button class="btn btn-primary btn-large" (click)="populate()" [disabled]="!curProject ">Populate</button>
- <button *ngIf="debugEnable" class="btn btn-primary btn-large" (click)="execCmd()" [disabled]="!curProject ">Execute command</button>
- </div>
- </div>
- </div>
- </form>
-
- <div style="margin-left: 2em; margin-right: 2em; ">
- <div class="row ">
- <div class="col-xs-10">
- <div class="row ">
- <div class="col-xs-4">
- <label>Command Output</label>
- </div>
- <div class="col-xs-8" style="font-size:x-small; margin-top:5px;">
- {{ cmdInfo }}
- </div>
- </div>
- </div>
- <div class="col-xs-2">
- <button class="btn btn-link pull-right " (click)="reset() "><span class="fa fa-eraser fa-size-x2"></span></button>
- </div>
- </div>
- <div class="row ">
- <div class="col-xs-12 text-center ">
- <textarea rows="20" class="textarea-scroll" #scrollOutput>{{ cmdOutput }}</textarea>
- </div>
- </div>
- </div>
- </div>
-</div>
diff --git a/webapp/src/app/devel/build/build.component.ts b/webapp/src/app/devel/build/build.component.ts
deleted file mode 100644
index 49f42eb..0000000
--- a/webapp/src/app/devel/build/build.component.ts
+++ /dev/null
@@ -1,193 +0,0 @@
-import { Component, ViewEncapsulation, AfterViewChecked, ElementRef, ViewChild, OnInit, Input } from '@angular/core';
-import { Observable } from 'rxjs/Observable';
-import { FormControl, FormGroup, Validators, FormBuilder } from '@angular/forms';
-import { CookieService } from 'ngx-cookie';
-
-import 'rxjs/add/operator/scan';
-import 'rxjs/add/operator/startWith';
-
-import { XDSAgentService, ICmdOutput } from '../../services/xdsagent.service';
-import { ProjectService, IProject } from '../../services/project.service';
-import { AlertService, IAlert } from '../../services/alert.service';
-import { SdkService } from '../../services/sdk.service';
-
-@Component({
- selector: 'xds-panel-build',
- templateUrl: './build.component.html',
- styleUrls: ['./build.component.css'],
-encapsulation: ViewEncapsulation.None
-})
-
-export class BuildComponent implements OnInit, AfterViewChecked {
- @ViewChild('scrollOutput') private scrollContainer: ElementRef;
-
- @Input() curProject: IProject;
-
- public buildForm: FormGroup;
- public subpathCtrl = new FormControl('', Validators.required);
- public debugEnable = false;
- public buildIsCollapsed = false;
- public cmdOutput: string;
- public cmdInfo: string;
-
- private startTime: Map<string, number> = new Map<string, number>();
-
- constructor(
- private xdsSvr: XDSAgentService,
- private fb: FormBuilder,
- private alertSvr: AlertService,
- private sdkSvr: SdkService,
- private cookie: CookieService,
- ) {
- this.cmdOutput = '';
- this.cmdInfo = ''; // TODO: to be remove (only for debug)
- this.buildForm = fb.group({
- subpath: this.subpathCtrl,
- cmdClean: ['', Validators.nullValidator],
- cmdPrebuild: ['', Validators.nullValidator],
- cmdBuild: ['', Validators.nullValidator],
- cmdPopulate: ['', Validators.nullValidator],
- cmdArgs: ['', Validators.nullValidator],
- envVars: ['', Validators.nullValidator],
- });
- }
-
- ngOnInit() {
- // Set default settings
- // TODO save & restore values from cookies
- this.buildForm.patchValue({
- subpath: '',
- cmdClean: 'rm -rf build',
- cmdPrebuild: 'mkdir -p build && cd build && cmake ..',
- cmdBuild: 'cd build && make',
- cmdPopulate: 'cd build && make remote-target-populate',
- cmdArgs: '',
- envVars: '',
- });
-
- // Command output data tunneling
- this.xdsSvr.CmdOutput$.subscribe(data => {
- this.cmdOutput += data.stdout;
- this.cmdOutput += data.stderr;
- });
-
- // Command exit
- this.xdsSvr.CmdExit$.subscribe(exit => {
- if (this.startTime.has(exit.cmdID)) {
- this.cmdInfo = 'Last command duration: ' + this._computeTime(this.startTime.get(exit.cmdID));
- this.startTime.delete(exit.cmdID);
- }
-
- if (exit && exit.code !== 0) {
- this.cmdOutput += '--- Command exited with code ' + exit.code + ' ---\n\n';
- }
- });
-
- this._scrollToBottom();
-
- // only use for debug
- this.debugEnable = (this.cookie.get('debug_build') === '1');
- }
-
- ngAfterViewChecked() {
- this._scrollToBottom();
- }
-
- reset() {
- this.cmdOutput = '';
- }
-
- clean() {
- this._exec(
- this.buildForm.value.cmdClean,
- this.buildForm.value.subpath,
- [],
- this.buildForm.value.envVars);
- }
-
- preBuild() {
- this._exec(
- this.buildForm.value.cmdPrebuild,
- this.buildForm.value.subpath,
- [],
- this.buildForm.value.envVars);
- }
-
- build() {
- this._exec(
- this.buildForm.value.cmdBuild,
- this.buildForm.value.subpath,
- [],
- this.buildForm.value.envVars
- );
- }
-
- populate() {
- this._exec(
- this.buildForm.value.cmdPopulate,
- this.buildForm.value.subpath,
- [], // args
- this.buildForm.value.envVars
- );
- }
-
- execCmd() {
- this._exec(
- this.buildForm.value.cmdArgs,
- this.buildForm.value.subpath,
- [],
- this.buildForm.value.envVars
- );
- }
-
- private _exec(cmd: string, dir: string, args: string[], env: string) {
- if (!this.curProject) {
- this.alertSvr.warning('No active project', true);
- }
-
- const prjID = this.curProject.id;
-
- this.cmdOutput += this._outputHeader();
-
- const sdkid = this.sdkSvr.getCurrentId();
-
- // Detect key=value in env string to build array of string
- const envArr = [];
- env.split(';').forEach(v => envArr.push(v.trim()));
-
- const t0 = performance.now();
- this.cmdInfo = 'Start build of ' + prjID + ' at ' + t0;
-
- this.xdsSvr.exec(prjID, dir, cmd, sdkid, args, envArr)
- .subscribe(res => {
- this.startTime.set(String(res.cmdID), t0);
- },
- err => {
- this.cmdInfo = 'Last command duration: ' + this._computeTime(t0);
- this.alertSvr.error('ERROR: ' + err);
- });
- }
-
- private _scrollToBottom(): void {
- try {
- this.scrollContainer.nativeElement.scrollTop = this.scrollContainer.nativeElement.scrollHeight;
- } catch (err) { }
- }
-
- private _computeTime(t0: number, t1?: number): string {
- const enlap = Math.round((t1 || performance.now()) - t0);
- if (enlap < 1000.0) {
- return enlap.toFixed(2) + ' ms';
- } else {
- return (enlap / 1000.0).toFixed(3) + ' seconds';
- }
- }
-
- private _outputHeader(): string {
- return '--- ' + new Date().toString() + ' ---\n';
- }
-
- private _outputFooter(): string {
- return '\n';
- }
-}
diff --git a/webapp/src/app/devel/devel.component.css b/webapp/src/app/devel/devel.component.css
deleted file mode 100644
index 4b03dcb..0000000
--- a/webapp/src/app/devel/devel.component.css
+++ /dev/null
@@ -1,19 +0,0 @@
-.table-center {
- width: 60%;
- margin-left: auto;
- margin-right: auto;
-}
-
-.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;
-}
-
-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
deleted file mode 100644
index 495eed4..0000000
--- a/webapp/src/app/devel/devel.component.html
+++ /dev/null
@@ -1,40 +0,0 @@
-<div class="row">
- <div class="col-md-8">
- <table class="table table-borderless table-center">
- <tbody>
- <tr>
- <th style="border: none;">Project</th>
- <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>
- <ul *dropdownMenu class="dropdown-menu" role="menu">
- <li role="menuitem"><a class="dropdown-item" *ngFor="let prj of (Prjs$ | async)" [class.disabled]="!prj.isUsable"
- (click)="curPrj=prj">{{prj.label}}</a>
- </li>
-
- </ul>
- </div>
- <span *ngIf="!curPrj" style="color:red; font-style: italic;">
- No project detected, please create first a project using the configuration page.
- </span>
- </td>
- </tr>
- </tbody>
- </table>
- </div>
-</div>
-
-<div class="row">
- <!--<div class="col-md-8">-->
- <div class="col-md-12">
- <xds-panel-build [curProject]=curPrj></xds-panel-build>
- </div>
- <!-- TODO: disable for now
- <div class="col-md-4">
- <panel-deploy [curProject]=curPrj></panel-deploy>
- </div>
- -->
-</div>
diff --git a/webapp/src/app/devel/devel.component.spec.ts b/webapp/src/app/devel/devel.component.spec.ts
deleted file mode 100644
index 6483bf5..0000000
--- a/webapp/src/app/devel/devel.component.spec.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { DevelComponent } from './devel.component';
-
-describe('DevelComponent', () => {
- let component: DevelComponent;
- let fixture: ComponentFixture<DevelComponent>;
-
- beforeEach(async(() => {
- TestBed.configureTestingModule({
- declarations: [ DevelComponent ]
- })
- .compileComponents();
- }));
-
- beforeEach(() => {
- fixture = TestBed.createComponent(DevelComponent);
- component = fixture.componentInstance;
- fixture.detectChanges();
- });
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });
-});
diff --git a/webapp/src/app/devel/devel.component.ts b/webapp/src/app/devel/devel.component.ts
deleted file mode 100644
index eda03ef..0000000
--- a/webapp/src/app/devel/devel.component.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { Component, OnInit, ViewEncapsulation } from '@angular/core';
-import { Observable } from 'rxjs/Observable';
-
-import { ProjectService, IProject } from '../services/project.service';
-
-@Component({
- selector: 'xds-devel',
- templateUrl: './devel.component.html',
- styleUrls: ['./devel.component.css'],
- encapsulation: ViewEncapsulation.None
-})
-
-export class DevelComponent implements OnInit {
-
- curPrj: IProject;
- Prjs$: Observable<IProject[]>;
-
- constructor(private projectSvr: ProjectService) {
- }
-
- ngOnInit() {
- this.Prjs$ = this.projectSvr.Projects$;
- this.Prjs$.subscribe((prjs) => {
- // Select project if no one is selected or no project exists
- if (this.curPrj && 'id' in this.curPrj) {
- this.curPrj = prjs.find(p => p.id === this.curPrj.id) || prjs[0];
- } else if (this.curPrj == null) {
- this.curPrj = prjs[0];
- } else {
- this.curPrj = null;
- }
- });
- }
-}
diff --git a/webapp/src/app/home/home.component.css b/webapp/src/app/home/home.component.css
deleted file mode 100644
index 7831443..0000000
--- a/webapp/src/app/home/home.component.css
+++ /dev/null
@@ -1,18 +0,0 @@
-.wide img {
- width: 98%;
-}
-.carousel-item {
- max-height: 90%;
-}
-h1, h2, h3, h4, p {
- color: #330066;
-}
-.html-inner {
- color: #330066;
-}
-h1 {
- font-size: 4em;
-}
-p {
- font-size: 2.5em;
-}
diff --git a/webapp/src/app/home/home.component.html b/webapp/src/app/home/home.component.html
deleted file mode 100644
index 568fb9b..0000000
--- a/webapp/src/app/home/home.component.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<div class="wide">
- <carousel [interval]="carInterval" [(activeSlide)]="activeSlideIndex">
- <slide *ngFor="let sl of slides; let index=index">
- <img [src]="sl.img" [alt]="sl.imgAlt">
- <div class="carousel-caption">
- <h1 *ngIf="sl.hText">{{ sl.hText }}</h1>
- <h1 *ngIf="sl.hHtml" class="html-inner" [innerHtml]="sl.hHtml"></h1>
- <p *ngIf="sl.text">{{ sl.text }}</p>
- <div *ngIf="sl.html" class="html-inner" [innerHtml]="sl.html"></div>
- </div>
- </slide>
- </carousel>
-</div>
diff --git a/webapp/src/app/home/home.component.ts b/webapp/src/app/home/home.component.ts
deleted file mode 100644
index 66a2f30..0000000
--- a/webapp/src/app/home/home.component.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { Component, OnInit } from '@angular/core';
-
-export interface ISlide {
- img?: string;
- imgAlt?: string;
- hText?: string;
- hHtml?: string;
- text?: string;
- html?: string;
- btn?: string;
- btnHref?: string;
-}
-
-@Component({
- selector: 'xds-home',
- templateUrl: 'home.component.html',
- styleUrls: ['home.component.css']
-})
-
-export class HomeComponent {
-
- public carInterval = 4000;
- public activeSlideIndex = 0;
-
- // FIXME SEB - Add more slides and info
- public slides: ISlide[] = [
- {
- img: 'assets/images/iot-graphx.jpg',
- imgAlt: 'iot graphx image',
- hText: 'Welcome to XDS Dashboard !',
- text: 'X(cross) Development System allows developers to easily cross-compile applications.',
- },
- {
- img: 'assets/images/iot-graphx.jpg',
- imgAlt: 'iot graphx image',
- hText: 'Create, Build, Deploy, Enjoy !',
- },
- {
- img: 'assets/images/iot-graphx.jpg',
- imgAlt: 'iot graphx image',
- hHtml: '<p>To Start: click on <i class=\'fa fa-cog\' style=\'color:#9d9d9d;\'></i> icon and add new folder</p>',
- }
- ];
-
- constructor() { }
-}
diff --git a/webapp/src/app/pages/build/build.component.html b/webapp/src/app/pages/build/build.component.html
new file mode 100644
index 0000000..a1ef62d
--- /dev/null
+++ b/webapp/src/app/pages/build/build.component.html
@@ -0,0 +1,63 @@
+<div class="row">
+ <div class="col-12">
+ <nb-card-body>
+ <nb-actions size="medium">
+ <nb-action>
+ <xds-project-select-dropdown></xds-project-select-dropdown>
+ </nb-action>
+ <nb-action>
+ <xds-sdk-select-dropdown></xds-sdk-select-dropdown>
+ </nb-action>
+ </nb-actions>
+ </nb-card-body>
+ </div>
+ <div class="col-md-12 col-lg-12 col-xxxl-6">
+ <nb-card size="xlarge">
+ <nb-tabset fullWidth>
+
+ <nb-tab tabTitle="Build">
+
+ <div class="row" style="margin-top:1em;">
+ <!-- FIXME SEB
+ <button class="btn pull-right " (click)="reset() ">
+ <span class="fa fa-eraser fa-size-x2"></span>
+ </button>
+ -->
+ <div class="col-md-12 text-center ">
+ <textarea rows="20" class="textarea-scroll" #scrollOutput>{{ cmdOutput }}</textarea>
+ </div>
+ </div>
+
+ <nb-card-body>
+ <nb-actions size="medium" fullWidth>
+ <nb-action (click)="clean()">
+ <i class="fa fa-eraser"></i>
+ <span>Clean</span>
+ </nb-action>
+ <nb-action (click)="preBuild()">
+ <i class="nb-list"></i>
+ <span>Pre-Build</span>
+ </nb-action>
+ <nb-action (click)="build()">
+ <i class="fa fa-wrench"></i>
+ <span>Build</span>
+ </nb-action>
+ <nb-action (click)="populate()">
+ <i class="fa fa-send"></i>
+ <span>Populate</span>
+ </nb-action>
+ </nb-actions>
+ </nb-card-body>
+
+ </nb-tab>
+
+ <nb-tab tabTitle="Deploy">
+ <span> Content deploy...</span>
+ </nb-tab>
+ <nb-tab tabTitle="Debug">
+ <span> Content debug...</span>
+ </nb-tab>
+ </nb-tabset>
+ </nb-card>
+ </div>
+</div>
diff --git a/webapp/src/app/pages/build/build.component.scss b/webapp/src/app/pages/build/build.component.scss
new file mode 100644
index 0000000..b256f66
--- /dev/null
+++ b/webapp/src/app/pages/build/build.component.scss
@@ -0,0 +1,37 @@
+@import '../../@theme/styles/themes';
+@import '~bootstrap/scss/mixins/breakpoints';
+@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
+@include nb-install-component() {
+ nb-tabset {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ }
+ nb-tab {
+ padding: nb-theme(padding);
+ }
+ /deep/ ngx-tab1,
+ /deep/ ngx-tab2 {
+ display: block;
+ padding: nb-theme(padding);
+ }
+ @include media-breakpoint-down(xs) {
+ nb-tabset /deep/ul {
+ font-size: 1rem;
+ padding: 0 0.25rem;
+ }
+ }
+}
+
+nb-action {
+ i {
+ font-size: 2rem;
+ margin-right: 0.5rem;
+ }
+}
+
+.textarea-scroll {
+ border-color: lightgray;
+ width: 97%;
+ overflow-y: scroll;
+}
diff --git a/webapp/src/app/devel/build/build.component.spec.ts b/webapp/src/app/pages/build/build.component.spec.ts
index 016192c..016192c 100644
--- a/webapp/src/app/devel/build/build.component.spec.ts
+++ b/webapp/src/app/pages/build/build.component.spec.ts
diff --git a/webapp/src/app/pages/build/build.component.ts b/webapp/src/app/pages/build/build.component.ts
new file mode 100644
index 0000000..5adb9bc
--- /dev/null
+++ b/webapp/src/app/pages/build/build.component.ts
@@ -0,0 +1,198 @@
+import { Component, ViewEncapsulation, AfterViewChecked, ElementRef, ViewChild, OnInit, Input } from '@angular/core';
+import { Observable } from 'rxjs/Observable';
+import { FormControl, FormGroup, Validators, FormBuilder } from '@angular/forms';
+import { CookieService } from 'ngx-cookie';
+
+import 'rxjs/add/operator/scan';
+import 'rxjs/add/operator/startWith';
+
+import { XDSAgentService, ICmdOutput } from '../../@core-xds/services/xdsagent.service';
+import { ProjectService, IProject } from '../../@core-xds/services/project.service';
+import { AlertService, IAlert } from '../../@core-xds/services/alert.service';
+import { SdkService } from '../../@core-xds/services/sdk.service';
+
+@Component({
+ selector: 'xds-panel-build',
+ templateUrl: './build.component.html',
+ styleUrls: ['./build.component.scss'],
+ encapsulation: ViewEncapsulation.None,
+})
+
+export class BuildComponent implements OnInit, AfterViewChecked {
+ @ViewChild('scrollOutput') private scrollContainer: ElementRef;
+
+ // FIXME workaround of https://github.com/angular/angular-cli/issues/2034
+ // should be removed with angular 5
+ // @Input() curProject: IProject;
+ @Input() curProject = <IProject>null;
+
+ public buildForm: FormGroup;
+ public subpathCtrl = new FormControl('', Validators.required);
+ public debugEnable = false;
+ public buildIsCollapsed = false;
+ public cmdOutput: string;
+ public cmdInfo: string;
+
+ private startTime: Map<string, number> = new Map<string, number>();
+
+ constructor(
+ private prjSvr: ProjectService,
+ private xdsSvr: XDSAgentService,
+ private fb: FormBuilder,
+ private alertSvr: AlertService,
+ private sdkSvr: SdkService,
+ private cookie: CookieService,
+ ) {
+ this.cmdOutput = '';
+ this.cmdInfo = ''; // TODO: to be remove (only for debug)
+ this.buildForm = fb.group({
+ subpath: this.subpathCtrl,
+ cmdClean: ['', Validators.nullValidator],
+ cmdPrebuild: ['', Validators.nullValidator],
+ cmdBuild: ['', Validators.nullValidator],
+ cmdPopulate: ['', Validators.nullValidator],
+ cmdArgs: ['', Validators.nullValidator],
+ envVars: ['', Validators.nullValidator],
+ });
+ }
+
+ ngOnInit() {
+ // Set default settings
+ // TODO save & restore values from cookies
+ this.buildForm.patchValue({
+ subpath: '',
+ cmdClean: 'rm -rf build',
+ cmdPrebuild: 'mkdir -p build && cd build && cmake ..',
+ cmdBuild: 'cd build && make',
+ cmdPopulate: 'cd build && make remote-target-populate',
+ cmdArgs: '',
+ envVars: '',
+ });
+
+ // Command output data tunneling
+ this.xdsSvr.CmdOutput$.subscribe(data => {
+ this.cmdOutput += data.stdout;
+ this.cmdOutput += data.stderr;
+ });
+
+ // Command exit
+ this.xdsSvr.CmdExit$.subscribe(exit => {
+ if (this.startTime.has(exit.cmdID)) {
+ this.cmdInfo = 'Last command duration: ' + this._computeTime(this.startTime.get(exit.cmdID));
+ this.startTime.delete(exit.cmdID);
+ }
+
+ if (exit && exit.code !== 0) {
+ this.cmdOutput += '--- Command exited with code ' + exit.code + ' ---\n\n';
+ }
+ });
+
+ this._scrollToBottom();
+
+ // only use for debug
+ this.debugEnable = (this.cookie.get('debug_build') === '1');
+ }
+
+ ngAfterViewChecked() {
+ this._scrollToBottom();
+ }
+
+ reset() {
+ this.cmdOutput = '';
+ }
+
+ clean() {
+ this._exec(
+ this.buildForm.value.cmdClean,
+ this.buildForm.value.subpath,
+ [],
+ this.buildForm.value.envVars);
+ }
+
+ preBuild() {
+ this._exec(
+ this.buildForm.value.cmdPrebuild,
+ this.buildForm.value.subpath,
+ [],
+ this.buildForm.value.envVars);
+ }
+
+ build() {
+ this._exec(
+ this.buildForm.value.cmdBuild,
+ this.buildForm.value.subpath,
+ [],
+ this.buildForm.value.envVars
+ );
+ }
+
+ populate() {
+ this._exec(
+ this.buildForm.value.cmdPopulate,
+ this.buildForm.value.subpath,
+ [], // args
+ this.buildForm.value.envVars
+ );
+ }
+
+ execCmd() {
+ this._exec(
+ this.buildForm.value.cmdArgs,
+ this.buildForm.value.subpath,
+ [],
+ this.buildForm.value.envVars
+ );
+ }
+
+ private _exec(cmd: string, dir: string, args: string[], env: string) {
+ if (!this.curProject) {
+ this.alertSvr.warning('No active project', true);
+ }
+
+ // const prjID = this.curProject.id;
+ const prjID = this.prjSvr.getCurrent().id;
+
+ this.cmdOutput += this._outputHeader();
+
+ const sdkid = this.sdkSvr.getCurrentId();
+
+ // Detect key=value in env string to build array of string
+ const envArr = [];
+ env.split(';').forEach(v => envArr.push(v.trim()));
+
+ const t0 = performance.now();
+ this.cmdInfo = 'Start build of ' + prjID + ' at ' + t0;
+
+ this.xdsSvr.exec(prjID, dir, cmd, sdkid, args, envArr)
+ .subscribe(res => {
+ this.startTime.set(String(res.cmdID), t0);
+ },
+ err => {
+ this.cmdInfo = 'Last command duration: ' + this._computeTime(t0);
+ this.alertSvr.error('ERROR: ' + err);
+ });
+ }
+
+ private _scrollToBottom(): void {
+ try {
+ this.scrollContainer.nativeElement.scrollTop = this.scrollContainer.nativeElement.scrollHeight;
+ } catch (err) { }
+ }
+
+ private _computeTime(t0: number, t1?: number): string {
+ const enlap = Math.round((t1 || performance.now()) - t0);
+ if (enlap < 1000.0) {
+ return enlap.toFixed(2) + ' ms';
+ } else {
+ return (enlap / 1000.0).toFixed(3) + ' seconds';
+ }
+ }
+
+ private _outputHeader(): string {
+ return '--- ' + new Date().toString() + ' ---\n';
+ }
+
+ private _outputFooter(): string {
+ return '\n';
+ }
+}
diff --git a/webapp/src/app/pages/build/build.module.ts b/webapp/src/app/pages/build/build.module.ts
new file mode 100644
index 0000000..ac1dfab
--- /dev/null
+++ b/webapp/src/app/pages/build/build.module.ts
@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+import { ThemeModule } from '../../@theme/theme.module';
+
+import { BuildComponent } from './build.component';
+import { ProjectSelectDropdownComponent } from './settings/project-select-dropdown.component';
+import { SdkSelectDropdownComponent } from './settings/sdk-select-dropdown.component';
+
+@NgModule({
+ imports: [
+ ThemeModule,
+ ],
+ declarations: [
+ BuildComponent,
+ ProjectSelectDropdownComponent,
+ SdkSelectDropdownComponent,
+ ],
+ entryComponents: [
+ ],
+})
+export class BuildModule { }
diff --git a/webapp/src/app/pages/build/settings/project-select-dropdown.component.ts b/webapp/src/app/pages/build/settings/project-select-dropdown.component.ts
new file mode 100644
index 0000000..da3580a
--- /dev/null
+++ b/webapp/src/app/pages/build/settings/project-select-dropdown.component.ts
@@ -0,0 +1,40 @@
+import { Component, OnInit, Input } from '@angular/core';
+
+import { IProject, ProjectService } from '../../../@core-xds/services/project.service';
+
+@Component({
+ selector: 'xds-project-select-dropdown',
+ template: `
+ <div class="form-group">
+ <label>Project</label>
+ <select class="form-control">
+ <option *ngFor="let prj of projects" (click)="select(prj)">{{prj.label}}</option>
+ </select>
+ </div>
+ `,
+})
+export class ProjectSelectDropdownComponent implements OnInit {
+
+ projects: IProject[];
+ curPrj: IProject;
+
+ constructor(private prjSvr: ProjectService) { }
+
+ ngOnInit() {
+ this.curPrj = this.prjSvr.getCurrent();
+ this.prjSvr.Projects$.subscribe((s) => {
+ if (s) {
+ this.projects = s;
+ if (this.curPrj === null || s.indexOf(this.curPrj) === -1) {
+ this.prjSvr.setCurrent(this.curPrj = s.length ? s[0] : null);
+ }
+ }
+ });
+ }
+
+ select(s) {
+ this.prjSvr.setCurrent(this.curPrj = s);
+ }
+}
+
+
diff --git a/webapp/src/app/sdks/sdkSelectDropdown.component.ts b/webapp/src/app/pages/build/settings/sdk-select-dropdown.component.ts
index 7cd2dc7..562386d 100644
--- a/webapp/src/app/sdks/sdkSelectDropdown.component.ts
+++ b/webapp/src/app/pages/build/settings/sdk-select-dropdown.component.ts
@@ -1,21 +1,17 @@
import { Component, OnInit, Input } from '@angular/core';
-import { ISdk, SdkService } from '../services/sdk.service';
+import { ISdk, SdkService } from '../../../@core-xds/services/sdk.service';
@Component({
selector: 'xds-sdk-select-dropdown',
template: `
- <div class="btn-group" dropdown *ngIf="curSdk" >
- <button dropdownToggle type="button" class="btn btn-primary dropdown-toggle" style="width: 20em;">
- {{curSdk.name}} <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 sdk of sdks" (click)="select(sdk)">
- {{sdk.name}}</a>
- </li>
- </ul>
- </div>
- `
+ <div class="form-group">
+ <label>SDK</label>
+ <select class="form-control">
+ <option *ngFor="let sdk of sdks" (click)="select(sdk)">{{sdk.name}}</option>
+ </select>
+ </div>
+ `,
})
export class SdkSelectDropdownComponent implements OnInit {
diff --git a/webapp/src/app/pages/config/config-global/config-global.component.html b/webapp/src/app/pages/config/config-global/config-global.component.html
new file mode 100644
index 0000000..0038510
--- /dev/null
+++ b/webapp/src/app/pages/config/config-global/config-global.component.html
@@ -0,0 +1,37 @@
+<div class="row">
+ <div class="col-md-12">
+ <nb-card>
+ <nb-card-header>Global Configuration</nb-card-header>
+ <nb-card-body>
+
+ <form (ngSubmit)="onSubmit()" #ConfigGlobalForm="ngForm">
+ <div class="form-group row">
+ <label class="col-sm-3 col-form-label">Language</label>
+ <div class="col-sm-9">
+ <select class="form-control" (ngModelChange)="configFormChanged=true">
+ <option>English</option>
+ <!-- FIXME: implement i18n and add | translate
+ <option>French</option> -->
+ </select>
+ </div>
+ </div>
+
+ <div class="form-group row">
+ <label class="col-sm-3 col-form-label">Theme</label>
+ <div class="col-sm-9">
+ <ngx-theme-switcher id="theme-switcher"></ngx-theme-switcher>
+ </div>
+ </div>
+
+ <!--
+ <div class="form-group row">
+ <div class="offset-sm-3 col-sm-9">
+ <button type="submit" class="btn btn-primary" [disabled]="!configFormChanged">Apply</button>
+ </div>
+ </div>
+ -->
+ </form>
+ </nb-card-body>
+ </nb-card>
+ </div>
+</div>
diff --git a/webapp/src/app/pages/config/config-global/config-global.component.scss b/webapp/src/app/pages/config/config-global/config-global.component.scss
new file mode 100644
index 0000000..955c54e
--- /dev/null
+++ b/webapp/src/app/pages/config/config-global/config-global.component.scss
@@ -0,0 +1,20 @@
+.full-width {
+ flex: 1;
+ min-width: 220px;
+}
+
+nb-checkbox {
+ margin-bottom: 1rem;
+}
+
+.form-inline > * {
+ margin: 0 1.5rem 1.5rem 0;
+}
+
+nb-card.inline-form-card nb-card-body {
+ padding-bottom: 0;
+}
+
+#theme-switcher {
+ align-items: left;
+}
diff --git a/webapp/src/app/pages/config/config-global/config-global.component.ts b/webapp/src/app/pages/config/config-global/config-global.component.ts
new file mode 100644
index 0000000..fcd8b62
--- /dev/null
+++ b/webapp/src/app/pages/config/config-global/config-global.component.ts
@@ -0,0 +1,25 @@
+import { Component, OnInit } from '@angular/core';
+
+import { ConfigService, IConfig } from '../../../@core-xds/services/config.service';
+
+@Component({
+ selector: 'xds-config-global',
+ styleUrls: ['./config-global.component.scss'],
+ templateUrl: './config-global.component.html',
+})
+export class ConfigGlobalComponent implements OnInit {
+
+ public configFormChanged = false;
+
+ constructor(
+ private configSvr: ConfigService,
+ ) {
+ }
+
+ ngOnInit() {
+ }
+
+ onSubmit() {
+ }
+}
+
diff --git a/webapp/src/app/pages/config/config-routing.module.ts b/webapp/src/app/pages/config/config-routing.module.ts
new file mode 100644
index 0000000..4e7cf27
--- /dev/null
+++ b/webapp/src/app/pages/config/config-routing.module.ts
@@ -0,0 +1,32 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+import { ConfigComponent } from './config.component';
+import { ConfigGlobalComponent } from './config-global/config-global.component';
+import { ConfigXdsComponent } from './config-xds/config-xds.component';
+
+const routes: Routes = [{
+ path: '',
+ component: ConfigComponent,
+ children: [
+ {
+ path: 'global',
+ component: ConfigGlobalComponent,
+ }, {
+ path: 'xds',
+ component: ConfigXdsComponent,
+ },
+ ],
+}];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule],
+})
+export class ConfigRoutingModule { }
+
+export const routedConfig = [
+ ConfigComponent,
+ ConfigGlobalComponent,
+ ConfigXdsComponent,
+];
diff --git a/webapp/src/app/pages/config/config-xds/config-xds.component.html b/webapp/src/app/pages/config/config-xds/config-xds.component.html
new file mode 100644
index 0000000..31559e2
--- /dev/null
+++ b/webapp/src/app/pages/config/config-xds/config-xds.component.html
@@ -0,0 +1,34 @@
+<div class="row">
+ <div class="col-md-12">
+ <nb-card>
+ <nb-card-header>XDS Server Configuration</nb-card-header>
+ <nb-card-body>
+ <form (ngSubmit)="onSubmit()" #ConfigXdsForm="ngForm">
+ <div class="form-group row">
+ <label class="col-sm-3 col-form-label">XDS Server URL</label>
+ <div class="col-sm-8">
+ <input type="url" class="form-control" [ngClass]="{ 'form-control-danger': !server.connected }" id="inputServerUrl" [(ngModel)]="xdsServerUrl" name="serverUrl" (ngModelChange)="configFormChanged=true" [disabled]="connecting">
+ </div>
+ <div class="col-sm-1">
+ <span *ngIf="!connecting" class="fa fa-fw fa-exchange fa-size-x2 vcenter" [style.color]="server.connected?'green':'red'"></span>
+ <span *ngIf="connecting" class="fa fa-gear faa-spin animated fa-size-x2 vcenter"></span>
+ </div>
+ </div>
+ <div class="form-group row">
+ <label class="col-sm-3 col-form-label">XDS Server connection retry</label>
+ <div class="col-sm-8">
+ <input type="number" class="form-control" id="inputServerConnRetry" [(ngModel)]="server.connRetry" name="serverRetry" (ngModelChange)="configFormChanged=true">
+ </div>
+ </div>
+
+ <div class="form-group row">
+ <div class="offset-sm-3 col-sm-9">
+ <button type="submit" class="btn btn-primary" [disabled]="
+ connecting || (server.connected && !configFormChanged)">Apply</button>
+ </div>
+ </div>
+ </form>
+ </nb-card-body>
+ </nb-card>
+ </div>
+</div>
diff --git a/webapp/src/app/pages/config/config-xds/config-xds.component.scss b/webapp/src/app/pages/config/config-xds/config-xds.component.scss
new file mode 100644
index 0000000..d7571b9
--- /dev/null
+++ b/webapp/src/app/pages/config/config-xds/config-xds.component.scss
@@ -0,0 +1,26 @@
+.full-width {
+ flex: 1;
+ min-width: 220px;
+}
+
+nb-checkbox {
+ margin-bottom: 1rem;
+}
+
+.form-inline > * {
+ margin: 0 1.5rem 1.5rem 0;
+}
+
+nb-card.inline-form-card nb-card-body {
+ padding-bottom: 0;
+}
+
+.fa-size-x2 {
+ font-size: 20px;
+}
+
+.vcenter {
+ //display: inline-block;
+ //vertical-align: middle;
+ margin-top: 33%;
+}
diff --git a/webapp/src/app/pages/config/config-xds/config-xds.component.ts b/webapp/src/app/pages/config/config-xds/config-xds.component.ts
new file mode 100644
index 0000000..ffd236d
--- /dev/null
+++ b/webapp/src/app/pages/config/config-xds/config-xds.component.ts
@@ -0,0 +1,63 @@
+import { Component, OnInit } from '@angular/core';
+import { Observable } from 'rxjs/Observable';
+
+import { XDSConfigService } from '../../../@core-xds/services/xds-config.service';
+import { IXDServerCfg } from '../../../@core-xds/services/xdsagent.service';
+import { AlertService, IAlert } from '../../../@core-xds/services/alert.service';
+import { NotificationsComponent } from '../../notifications/notifications.component';
+
+// Import RxJs required methods
+import 'rxjs/add/operator/map';
+import 'rxjs/add/operator/catch';
+
+@Component({
+ selector: 'xds-config-xds',
+ styleUrls: ['./config-xds.component.scss'],
+ templateUrl: './config-xds.component.html',
+})
+export class ConfigXdsComponent implements OnInit {
+
+ // TODO: cleanup agentStatus$: Observable<IAgentStatus>;
+ connecting = false;
+ xdsServerUrl = '';
+ server: IXDServerCfg;
+
+ configFormChanged = false;
+
+ constructor(
+ private XdsConfigSvr: XDSConfigService,
+ private alert: AlertService,
+ ) {
+ }
+
+ ngOnInit() {
+ // FIXME support multiple servers
+
+ this.server = this.XdsConfigSvr.getCurServer();
+ this.xdsServerUrl = this.server.url;
+
+ this.XdsConfigSvr.onCurServer().subscribe(svr => {
+ this.xdsServerUrl = svr.url;
+ this.server = svr;
+ });
+ }
+
+ onSubmit() {
+ if (!this.configFormChanged && this.server.connected) {
+ return;
+ }
+ this.configFormChanged = false;
+ this.connecting = true;
+ this.server.url = this.xdsServerUrl;
+ this.XdsConfigSvr.setCurServer(this.server)
+ .subscribe(cfg => {
+ this.connecting = false;
+ },
+ err => {
+ this.connecting = false;
+ this.alert.error(err);
+ });
+ }
+
+}
+
diff --git a/webapp/src/app/config/downloadXdsAgent.component.ts b/webapp/src/app/pages/config/config-xds/downloadXdsAgent.component.ts
index 3901331..3901331 100644
--- a/webapp/src/app/config/downloadXdsAgent.component.ts
+++ b/webapp/src/app/pages/config/config-xds/downloadXdsAgent.component.ts
diff --git a/webapp/src/app/pages/config/config.component.ts b/webapp/src/app/pages/config/config.component.ts
new file mode 100644
index 0000000..f52cab0
--- /dev/null
+++ b/webapp/src/app/pages/config/config.component.ts
@@ -0,0 +1,10 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'xds-config',
+ template: `
+ <router-outlet></router-outlet>
+ `,
+})
+export class ConfigComponent {
+}
diff --git a/webapp/src/app/pages/config/config.module.ts b/webapp/src/app/pages/config/config.module.ts
new file mode 100644
index 0000000..2fdaf94
--- /dev/null
+++ b/webapp/src/app/pages/config/config.module.ts
@@ -0,0 +1,15 @@
+import { NgModule } from '@angular/core';
+
+import { ThemeModule } from '../../@theme/theme.module';
+import { ConfigRoutingModule, routedConfig } from './config-routing.module';
+
+@NgModule({
+ imports: [
+ ThemeModule,
+ ConfigRoutingModule,
+ ],
+ declarations: [
+ ...routedConfig,
+ ]
+})
+export class ConfigModule { }
diff --git a/webapp/src/app/pages/dashboard/dashboard.component.html b/webapp/src/app/pages/dashboard/dashboard.component.html
new file mode 100644
index 0000000..9160019
--- /dev/null
+++ b/webapp/src/app/pages/dashboard/dashboard.component.html
@@ -0,0 +1,10 @@
+<div class="row">
+ Dashboard page...
+ <!--
+ <div class="col-xxxl-3 col-md-6">
+ <ngx-status-card title="Light" type="primary">
+ <i class="nb-lightbulb"></i>
+ </ngx-status-card>
+ </div>
+-->
+</div>
diff --git a/webapp/src/app/pages/dashboard/dashboard.component.scss b/webapp/src/app/pages/dashboard/dashboard.component.scss
new file mode 100644
index 0000000..6f1f0e0
--- /dev/null
+++ b/webapp/src/app/pages/dashboard/dashboard.component.scss
@@ -0,0 +1,16 @@
+@import '../../@theme/styles/themes';
+@import '~bootstrap/scss/mixins/breakpoints';
+@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
+
+@include nb-install-component() {
+ .solar-card nb-card-header {
+ border: none;
+ padding-bottom: 0;
+ }
+
+ @include media-breakpoint-down(is) {
+ /deep/ nb-card.large-card {
+ height: nb-theme(card-height-medium);
+ }
+ }
+}
diff --git a/webapp/src/app/pages/dashboard/dashboard.component.ts b/webapp/src/app/pages/dashboard/dashboard.component.ts
new file mode 100644
index 0000000..a4539cb
--- /dev/null
+++ b/webapp/src/app/pages/dashboard/dashboard.component.ts
@@ -0,0 +1,9 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'ngx-dashboard',
+ styleUrls: ['./dashboard.component.scss'],
+ templateUrl: './dashboard.component.html',
+})
+export class DashboardComponent {
+}
diff --git a/webapp/src/app/pages/dashboard/dashboard.module.ts b/webapp/src/app/pages/dashboard/dashboard.module.ts
new file mode 100644
index 0000000..725df7a
--- /dev/null
+++ b/webapp/src/app/pages/dashboard/dashboard.module.ts
@@ -0,0 +1,52 @@
+import { NgModule } from '@angular/core';
+import { AngularEchartsModule } from 'ngx-echarts';
+
+import { ThemeModule } from '../../@theme/theme.module';
+import { DashboardComponent } from './dashboard.component';
+import { StatusCardComponent } from './status-card/status-card.component';
+/*
+import { ContactsComponent } from './contacts/contacts.component';
+import { RoomsComponent } from './rooms/rooms.component';
+import { RoomSelectorComponent } from './rooms/room-selector/room-selector.component';
+import { TemperatureComponent } from './temperature/temperature.component';
+import { TemperatureDraggerComponent } from './temperature/temperature-dragger/temperature-dragger.component';
+import { TeamComponent } from './team/team.component';
+import { KittenComponent } from './kitten/kitten.component';
+import { SecurityCamerasComponent } from './security-cameras/security-cameras.component';
+import { ElectricityComponent } from './electricity/electricity.component';
+import { ElectricityChartComponent } from './electricity/electricity-chart/electricity-chart.component';
+import { WeatherComponent } from './weather/weather.component';
+import { SolarComponent } from './solar/solar.component';
+import { PlayerComponent } from './rooms/player/player.component';
+import { TrafficComponent } from './traffic/traffic.component';
+import { TrafficChartComponent } from './traffic/traffic-chart.component';
+*/
+
+@NgModule({
+ imports: [
+ ThemeModule,
+ AngularEchartsModule,
+ ],
+ declarations: [
+ DashboardComponent,
+ StatusCardComponent,
+ /*
+ TemperatureDraggerComponent,
+ ContactsComponent,
+ RoomSelectorComponent,
+ TemperatureComponent,
+ RoomsComponent,
+ TeamComponent,
+ KittenComponent,
+ SecurityCamerasComponent,
+ ElectricityComponent,
+ ElectricityChartComponent,
+ WeatherComponent,
+ PlayerComponent,
+ SolarComponent,
+ TrafficComponent,
+ TrafficChartComponent,
+ */
+ ],
+})
+export class DashboardModule { }
diff --git a/webapp/src/app/pages/dashboard/status-card/status-card.component.scss b/webapp/src/app/pages/dashboard/status-card/status-card.component.scss
new file mode 100644
index 0000000..08abc61
--- /dev/null
+++ b/webapp/src/app/pages/dashboard/status-card/status-card.component.scss
@@ -0,0 +1,142 @@
+@import '../../../@theme/styles/themes';
+@import '~@nebular/theme/styles/global/bootstrap/hero-buttons';
+
+@include nb-install-component() {
+ nb-card {
+ flex-direction: row;
+ align-items: center;
+ height: 6rem;
+ overflow: visible;
+
+ $bevel: btn-hero-bevel(nb-theme(card-bg));
+ $shadow: nb-theme(btn-hero-shadow);
+ box-shadow: $bevel, $shadow;
+
+ .icon-container {
+ height: 100%;
+ padding: 0.625rem;
+ }
+
+ .icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 5.75rem;
+ height: 4.75rem;
+ font-size: 3.75rem;
+ border-radius: nb-theme(card-border-radius);
+ transition: width 0.4s ease;
+ transform: translate3d(0, 0, 0);
+ -webkit-transform-style: preserve-3d;
+ -webkit-backface-visibility: hidden;
+ color: nb-theme(color-white);
+
+ &.primary {
+ @include btn-hero-primary-gradient();
+ @include btn-hero-primary-bevel-glow-shadow();
+ }
+ &.success {
+ @include btn-hero-success-gradient();
+ @include btn-hero-success-bevel-glow-shadow();
+ }
+ &.info {
+ @include btn-hero-info-gradient();
+ @include btn-hero-info-bevel-glow-shadow();
+ }
+ &.warning {
+ @include btn-hero-warning-gradient();
+ @include btn-hero-warning-bevel-glow-shadow();
+ }
+ }
+
+ &:hover {
+ background: lighten(nb-theme(card-bg), 5%);
+
+ .icon {
+ &.primary {
+ background-image: btn-hero-primary-light-gradient();
+ }
+ &.success {
+ background-image: btn-hero-success-light-gradient();
+ }
+ &.info {
+ background-image: btn-hero-info-light-gradient();
+ }
+ &.warning {
+ background-image: btn-hero-warning-light-gradient();
+ }
+ }
+ }
+
+ &.off {
+ color: nb-theme(card-fg);
+
+ .icon {
+ color: nb-theme(card-fg);
+
+ &.primary, &.success, &.info, &.warning {
+ box-shadow: none;
+ background-image: linear-gradient(to right, transparent, transparent);
+ }
+ }
+
+ .title {
+ color: nb-theme(card-fg);
+ }
+ }
+
+ .details {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ height: 100%;
+ padding: 0 0.5rem 0 0.75rem;
+ border-left: 1px solid transparent;
+ }
+
+ .title {
+ font-family: nb-theme(font-secondary);
+ font-size: 1.25rem;
+ font-weight: nb-theme(font-weight-bold);
+ color: nb-theme(card-fg-heading);
+ }
+
+ .status {
+ font-size: 1rem;
+ font-weight: nb-theme(font-weight-light);
+ text-transform: uppercase;
+ color: nb-theme(card-fg);
+ }
+ }
+
+ @include nb-for-theme(cosmic) {
+ nb-card {
+ &.off .icon-container {
+ border-right: 1px solid nb-theme(separator);
+ }
+
+ .icon-container {
+ padding: 0;
+ }
+
+ .details {
+ padding-left: 1.25rem;
+ }
+
+ .icon {
+ width: 7rem;
+ height: 100%;
+ font-size: 4.5rem;
+ border-radius: nb-theme(card-border-radius) 0 0 nb-theme(card-border-radius);
+ }
+
+ .title {
+ font-weight: nb-theme(font-weight-bolder);
+ }
+
+ .status {
+ font-weight: nb-theme(font-weight-light);
+ }
+ }
+ }
+}
diff --git a/webapp/src/app/pages/dashboard/status-card/status-card.component.ts b/webapp/src/app/pages/dashboard/status-card/status-card.component.ts
new file mode 100644
index 0000000..6260803
--- /dev/null
+++ b/webapp/src/app/pages/dashboard/status-card/status-card.component.ts
@@ -0,0 +1,26 @@
+import { Component, Input } from '@angular/core';
+
+@Component({
+ selector: 'ngx-status-card',
+ styleUrls: ['./status-card.component.scss'],
+ template: `
+ <nb-card (click)="on = !on" [ngClass]="{'off': !on}">
+ <div class="icon-container">
+ <div class="icon {{ type }}">
+ <ng-content></ng-content>
+ </div>
+ </div>
+
+ <div class="details">
+ <div class="title">{{ title }}</div>
+ <div class="status">{{ on ? 'ON' : 'OFF' }}</div>
+ </div>
+ </nb-card>
+ `,
+})
+export class StatusCardComponent {
+
+ @Input() title: string;
+ @Input() type: string;
+ @Input() on = true;
+}
diff --git a/webapp/src/app/pages/notifications/notifications.component.scss b/webapp/src/app/pages/notifications/notifications.component.scss
new file mode 100644
index 0000000..ce85e8e
--- /dev/null
+++ b/webapp/src/app/pages/notifications/notifications.component.scss
@@ -0,0 +1,28 @@
+@import '../../@theme/styles/themes';
+@import '~bootstrap/scss/mixins/breakpoints';
+@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
+
+@include nb-install-component() {
+
+ nb-card-footer {
+ padding-bottom: 0.25rem;
+
+ button {
+ margin: 0 1rem 1rem 0;
+ }
+ }
+
+ /* stylelint-disable */
+ toaster-container /deep/ {
+ #toast-container .toast-close-button {
+ right: 0;
+ }
+ }
+ /* stylelint-enable */
+
+ @include media-breakpoint-down(xs) {
+ .dropdown-toggle {
+ font-size: 0.75rem;
+ }
+ }
+}
diff --git a/webapp/src/app/pages/notifications/notifications.component.ts b/webapp/src/app/pages/notifications/notifications.component.ts
new file mode 100644
index 0000000..accd150
--- /dev/null
+++ b/webapp/src/app/pages/notifications/notifications.component.ts
@@ -0,0 +1,65 @@
+import { Component } from '@angular/core';
+import { ToasterService, ToasterConfig, Toast, BodyOutputType } from 'angular2-toaster';
+import { Observable } from 'rxjs/Observable';
+import { AlertService, IAlert } from '../../@core-xds/services/alert.service';
+
+import 'style-loader!angular2-toaster/toaster.css';
+
+@Component({
+ selector: 'ngx-notifications',
+ styleUrls: ['./notifications.component.scss'],
+ template: '<toaster-container [toasterconfig]="config"></toaster-container>',
+})
+export class NotificationsComponent {
+
+ config: ToasterConfig;
+
+ private position = 'toast-top-full-width';
+ private animationType = 'slideDown';
+ private toastsLimit = 10;
+ private toasterService: ToasterService;
+ private alerts$: Observable<IAlert[]>;
+
+ constructor(
+ toasterService: ToasterService,
+ private alertSvr: AlertService,
+ ) {
+ this.toasterService = toasterService;
+
+ this.alertSvr.alerts.subscribe(alerts => {
+ if (alerts.length === 0) {
+ this.clearToasts();
+ } else {
+ alerts.forEach(al => {
+ const title = al.type.toUpperCase();
+ this.showToast(al.type, title, al.msg, al.dismissTimeout);
+ });
+ }
+ });
+ }
+
+ private showToast(type: string, title: string, body: string, tmo: number) {
+ this.config = new ToasterConfig({
+ positionClass: this.position,
+ timeout: tmo,
+ newestOnTop: true,
+ tapToDismiss: true, // is Hide OnClick
+ preventDuplicates: false,
+ animation: this.animationType,
+ limit: this.toastsLimit,
+ });
+ const toast: Toast = {
+ type: type,
+ title: title,
+ body: body,
+ timeout: tmo,
+ showCloseButton: true,
+ bodyOutputType: BodyOutputType.TrustedHtml,
+ };
+ this.toasterService.popAsync(toast);
+ }
+
+ clearToasts() {
+ this.toasterService.clear();
+ }
+}
diff --git a/webapp/src/app/pages/pages-menu.ts b/webapp/src/app/pages/pages-menu.ts
new file mode 100644
index 0000000..1a3dd97
--- /dev/null
+++ b/webapp/src/app/pages/pages-menu.ts
@@ -0,0 +1,201 @@
+import { NbMenuItem } from '@nebular/theme';
+
+export const MENU_ITEMS: NbMenuItem[] = [
+ {
+ title: 'XDS-Dashboard',
+ icon: 'nb-home',
+ link: '/pages/dashboard',
+ home: true,
+ },
+ {
+ title: 'DEVELOPMENT',
+ group: true,
+ },
+ {
+ title: 'Projects',
+ icon: 'nb-keypad',
+ link: '/pages/projects',
+ },
+ {
+ title: 'SDKs',
+ icon: 'fa fa-file-archive-o',
+ link: '/pages/sdks',
+ },
+ {
+ title: 'Boards',
+ icon: 'fa fa-microchip',
+ children: [
+ ],
+ },
+ {
+ title: 'Build',
+ icon: 'fa fa-cogs',
+ link: '/pages/build',
+ },
+ {
+ title: 'MISC',
+ group: true,
+ },
+
+ {
+ title: 'Configuration',
+ icon: 'fa fa-sliders',
+ link: '/pages/config',
+ children: [
+ {
+ title: 'Global',
+ link: '/pages/config/global',
+ },
+ {
+ title: 'XDS Server',
+ link: '/pages/config/xds',
+ },
+ ],
+ },
+ /*
+ {
+ title: 'UI Features',
+ icon: 'nb-keypad',
+ link: '/pages/ui-features',
+ children: [
+ {
+ title: 'Buttons',
+ link: '/pages/ui-features/buttons',
+ },
+ {
+ title: 'Grid',
+ link: '/pages/ui-features/grid',
+ },
+ {
+ title: 'Icons',
+ link: '/pages/ui-features/icons',
+ },
+ {
+ title: 'Modals',
+ link: '/pages/ui-features/modals',
+ },
+ {
+ title: 'Typography',
+ link: '/pages/ui-features/typography',
+ },
+ {
+ title: 'Animated Searches',
+ link: '/pages/ui-features/search-fields',
+ },
+ {
+ title: 'Tabs',
+ link: '/pages/ui-features/tabs',
+ },
+ ],
+ },
+ {
+ title: 'Forms',
+ icon: 'nb-compose',
+ children: [
+ {
+ title: 'Form Inputs',
+ link: '/pages/forms/inputs',
+ },
+ {
+ title: 'Form Layouts',
+ link: '/pages/forms/layouts',
+ },
+ ],
+ },
+ {
+ title: 'Components',
+ icon: 'nb-gear',
+ children: [
+ {
+ title: 'Tree',
+ link: '/pages/components/tree',
+ }, {
+ title: 'Notifications',
+ link: '/pages/components/notifications',
+ },
+ ],
+ },
+ {
+ title: 'Maps',
+ icon: 'nb-location',
+ children: [
+ {
+ title: 'Google Maps',
+ link: '/pages/maps/gmaps',
+ },
+ {
+ title: 'Leaflet Maps',
+ link: '/pages/maps/leaflet',
+ },
+ {
+ title: 'Bubble Maps',
+ link: '/pages/maps/bubble',
+ },
+ ],
+ },
+ {
+ title: 'Charts',
+ icon: 'nb-bar-chart',
+ children: [
+ {
+ title: 'Echarts',
+ link: '/pages/charts/echarts',
+ },
+ {
+ title: 'Charts.js',
+ link: '/pages/charts/chartjs',
+ },
+ {
+ title: 'D3',
+ link: '/pages/charts/d3',
+ },
+ ],
+ },
+ {
+ title: 'Editors',
+ icon: 'nb-title',
+ children: [
+ {
+ title: 'TinyMCE',
+ link: '/pages/editors/tinymce',
+ },
+ {
+ title: 'CKEditor',
+ link: '/pages/editors/ckeditor',
+ },
+ ],
+ },
+ {
+ title: 'Tables',
+ icon: 'nb-tables',
+ children: [
+ {
+ title: 'Smart Table',
+ link: '/pages/tables/smart-table',
+ },
+ ],
+ },
+ */
+ {
+ title: 'Auth',
+ icon: 'nb-locked',
+ children: [
+ {
+ title: 'Login',
+ link: '/auth/login',
+ },
+ {
+ title: 'Register',
+ link: '/auth/register',
+ },
+ {
+ title: 'Request Password',
+ link: '/auth/request-password',
+ },
+ {
+ title: 'Reset Password',
+ link: '/auth/reset-password',
+ },
+ ],
+ },
+];
diff --git a/webapp/src/app/pages/pages-routing.module.ts b/webapp/src/app/pages/pages-routing.module.ts
new file mode 100644
index 0000000..11834e8
--- /dev/null
+++ b/webapp/src/app/pages/pages-routing.module.ts
@@ -0,0 +1,41 @@
+import { RouterModule, Routes } from '@angular/router';
+import { NgModule } from '@angular/core';
+
+import { PagesComponent } from './pages.component';
+import { DashboardComponent } from './dashboard/dashboard.component';
+import { ProjectsComponent } from './projects/projects.component';
+import { SdksComponent } from './sdks/sdks.component';
+import { BuildComponent } from './build/build.component';
+
+const routes: Routes = [{
+ path: '',
+ component: PagesComponent,
+ children: [{
+ path: 'dashboard',
+ component: DashboardComponent,
+ }, {
+ path: 'projects',
+ component: ProjectsComponent,
+ }, {
+ path: 'sdks',
+ component: SdksComponent,
+ }, {
+ path: 'build',
+ component: BuildComponent,
+ }, {
+ path: 'config',
+ loadChildren: './config/config.module#ConfigModule',
+ },
+ {
+ path: '',
+ redirectTo: 'dashboard',
+ pathMatch: 'full',
+ }],
+}];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule],
+})
+export class PagesRoutingModule {
+}
diff --git a/webapp/src/app/pages/pages.component.ts b/webapp/src/app/pages/pages.component.ts
new file mode 100644
index 0000000..a343565
--- /dev/null
+++ b/webapp/src/app/pages/pages.component.ts
@@ -0,0 +1,18 @@
+import { Component } from '@angular/core';
+
+import { MENU_ITEMS } from './pages-menu';
+
+@Component({
+ selector: 'ngx-pages',
+ template: `
+ <ngx-notifications></ngx-notifications>
+ <ngx-xds-layout>
+ <nb-menu [items]="menu"></nb-menu>
+ <router-outlet></router-outlet>
+ </ngx-xds-layout>
+ `,
+})
+export class PagesComponent {
+
+ menu = MENU_ITEMS;
+}
diff --git a/webapp/src/app/pages/pages.module.ts b/webapp/src/app/pages/pages.module.ts
new file mode 100644
index 0000000..5e4f3be
--- /dev/null
+++ b/webapp/src/app/pages/pages.module.ts
@@ -0,0 +1,33 @@
+import { NgModule } from '@angular/core';
+import { ToasterModule } from 'angular2-toaster';
+
+import { PagesComponent } from './pages.component';
+import { DashboardModule } from './dashboard/dashboard.module';
+import { BuildModule } from './build/build.module';
+import { ProjectsModule } from './projects/projects.module';
+import { SdksModule } from './sdks/sdks.module';
+import { PagesRoutingModule } from './pages-routing.module';
+import { NotificationsComponent } from './notifications/notifications.component';
+import { ThemeModule } from '../@theme/theme.module';
+
+const PAGES_COMPONENTS = [
+ PagesComponent,
+ NotificationsComponent,
+];
+
+@NgModule({
+ imports: [
+ PagesRoutingModule,
+ ThemeModule,
+ BuildModule,
+ DashboardModule,
+ ProjectsModule,
+ SdksModule,
+ ToasterModule,
+ ],
+ declarations: [
+ ...PAGES_COMPONENTS,
+ ],
+})
+export class PagesModule {
+}
diff --git a/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.html b/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.html
new file mode 100644
index 0000000..e2c6748
--- /dev/null
+++ b/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.html
@@ -0,0 +1,51 @@
+<div class="modal-header">
+ <span>Add a new project</span>
+ <button class="close" aria-label="Close" (click)="closeModal()">
+ <span aria-hidden="true">&times;</span>
+ </button>
+</div>
+
+<div class="modal-body row">
+ <div class="col-12">
+ <form [formGroup]="addProjectForm" (ngSubmit)="onSubmit()">
+
+ <div class="form-group row">
+ <label for="sharing-type" class="col-sm-3 col-form-label">Sharing Type</label>
+ <div class="col-sm-9">
+ <select id="select-sharing-type" class="form-control" formControlName="type">
+ <option *ngFor="let t of projectTypes" [value]="t.value">{{t.display}}
+ </option>
+ </select>
+ </div>
+ </div>
+
+ <div class="form-group row">
+ <label for="select-local-path" class="col-sm-3 col-form-label">Local Path</label>
+ <div class="col-sm-9">
+ <input type="text" id="inputLocalPath" class="form-control" formControlName="pathCli" placeholder="/tmp/myProject" (change)="onChangeLocalProject($event)">
+ </div>
+ </div>
+
+ <div class="form-group row">
+ <label for="select-server-path" class="col-sm-3 col-form-label">Server Path</label>
+ <div class="col-sm-9">
+ <input type="text" id="inputServerPath" class="form-control" formControlName="pathSvr">
+ </div>
+ </div>
+
+ <div class="form-group row">
+ <label for="select-label" class="col-sm-3 col-form-label">Label</label>
+ <div class="col-sm-9">
+ <input type="text" id="inputLabel" class="form-control" formControlName="label" (keyup)="onKeyLabel($event)">
+ </div>
+ </div>
+
+ <div class="offset-sm-3 col-sm-9">
+ <button class="btn btn-md btn-secondary" (click)="cancelAction=true; closeModal()"> Cancel </button>
+ <button class="btn btn-md btn-primary" type="submit" [disabled]="!addProjectForm.valid">Add Folder</button>
+ </div>
+ </form>
+ </div>
+</div>
+<div class="modal-footer form-group">
+</div>
diff --git a/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.scss b/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.scss
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.scss
@@ -0,0 +1 @@
+
diff --git a/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.ts b/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.ts
new file mode 100644
index 0000000..640ac5c
--- /dev/null
+++ b/webapp/src/app/pages/projects/project-add-modal/project-add-modal.component.ts
@@ -0,0 +1,143 @@
+import { Component, Input, ViewChild, OnInit } from '@angular/core';
+import { Observable } from 'rxjs/Observable';
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import { FormControl, FormGroup, Validators, ValidationErrors, FormBuilder, ValidatorFn, AbstractControl } from '@angular/forms';
+
+// Import RxJs required methods
+import 'rxjs/add/operator/map';
+import 'rxjs/add/operator/filter';
+import 'rxjs/add/operator/debounceTime';
+
+import { AlertService, IAlert } from '../../../@core-xds/services/alert.service';
+import { ProjectService, IProject, ProjectType, ProjectTypes } from '../../../@core-xds/services/project.service';
+import { XDSConfigService } from '../../../@core-xds/services/xds-config.service';
+
+
+@Component({
+ selector: 'xds-project-add-modal',
+ templateUrl: 'project-add-modal.component.html',
+ styleUrls: ['project-add-modal.component.scss']
+})
+export class ProjectAddModalComponent implements OnInit {
+ // @Input('server-id') serverID: string;
+ private serverID: string;
+
+ cancelAction = false;
+ userEditedLabel = false;
+ projectTypes = ProjectTypes;
+
+ addProjectForm: FormGroup;
+ typeCtrl: FormControl;
+ pathCliCtrl: FormControl;
+ pathSvrCtrl: FormControl;
+
+ constructor(
+ private alert: AlertService,
+ private projectSvr: ProjectService,
+ private XdsConfigSvr: XDSConfigService,
+ private fb: FormBuilder,
+ private activeModal: NgbActiveModal
+ ) {
+ // Define types (first one is special/placeholder)
+ this.projectTypes.unshift({ value: ProjectType.UNSET, display: '--Select a type--' });
+
+ this.typeCtrl = new FormControl(this.projectTypes[0].value, this.validatorProjType);
+ this.pathCliCtrl = new FormControl('', this.validatorProjPath);
+ this.pathSvrCtrl = new FormControl({ value: '', disabled: true }, this.validatorProjPath);
+
+ this.addProjectForm = fb.group({
+ type: this.typeCtrl,
+ pathCli: this.pathCliCtrl,
+ pathSvr: this.pathSvrCtrl,
+ label: ['', Validators.nullValidator],
+ });
+ }
+
+
+ ngOnInit() {
+ // Update server ID
+ this.serverID = this.XdsConfigSvr.getCurServer().id;
+ this.XdsConfigSvr.onCurServer().subscribe(svr => this.serverID = svr.id);
+
+ // Auto create label name
+ this.pathCliCtrl.valueChanges
+ .debounceTime(100)
+ .filter(n => n)
+ .map(n => {
+ const last = n.split('/');
+ let nm = n;
+ if (last.length > 0) {
+ nm = last.pop();
+ if (nm === '' && last.length > 0) {
+ nm = last.pop();
+ }
+ }
+ return 'Project_' + nm;
+ })
+ .subscribe(value => {
+ if (value && !this.userEditedLabel) {
+ this.addProjectForm.patchValue({ label: value });
+ }
+ });
+
+ // Handle disabling of Server path
+ this.typeCtrl.valueChanges
+ .debounceTime(500)
+ .subscribe(valType => {
+ const dis = (valType === String(ProjectType.SYNCTHING));
+ this.pathSvrCtrl.reset({ value: '', disabled: dis });
+ });
+ }
+
+ closeModal() {
+ this.activeModal.close();
+ }
+
+ onKeyLabel(event: any) {
+ this.userEditedLabel = (this.addProjectForm.value.label !== '');
+ }
+
+ onChangeLocalProject(e) {
+ }
+
+ onSubmit() {
+ if (this.cancelAction) {
+ return;
+ }
+
+ const formVal = this.addProjectForm.value;
+
+ const type = formVal['type'].value;
+ this.projectSvr.Add({
+ serverId: this.serverID,
+ label: formVal['label'],
+ pathClient: formVal['pathCli'],
+ pathServer: formVal['pathSvr'],
+ type: formVal['type'],
+ // FIXME: allow to set defaultSdkID from New Project config panel
+ })
+ .subscribe(prj => {
+ this.alert.info('Project ' + prj.label + ' successfully created.');
+ this.closeModal();
+
+ // Reset Value for the next creation
+ this.addProjectForm.reset();
+ const selectedType = this.projectTypes[0].value;
+ this.addProjectForm.patchValue({ type: selectedType });
+
+ },
+ err => {
+ this.alert.error(err, 60);
+ this.closeModal();
+ });
+ }
+
+ private validatorProjType(g: FormGroup): ValidationErrors | null {
+ return (g.value !== ProjectType.UNSET) ? null : { validatorProjType: { valid: false } };
+ }
+
+ private validatorProjPath(g: FormGroup): ValidationErrors | null {
+ return (g.disabled || g.value !== '') ? null : { validatorProjPath: { valid: false } };
+ }
+
+}
diff --git a/webapp/src/app/pages/projects/project-card/project-card.component.html b/webapp/src/app/pages/projects/project-card/project-card.component.html
new file mode 100644
index 0000000..c54d581
--- /dev/null
+++ b/webapp/src/app/pages/projects/project-card/project-card.component.html
@@ -0,0 +1,65 @@
+<nb-card class="xds-projects">
+ <nb-card-header>
+
+ <div class="row">
+ <div class="col-12 col-md-8">
+ {{ project.label }}
+ </div>
+ <div class="col-6 col-md-4 text-right" role="group">
+ <button class="btn btn-outline-danger btn-tn btn-xds" (click)="delete(project)">
+ <span class="fa fa-trash fa-size-x2"></span>
+ </button>
+ </div>
+ </div>
+ </nb-card-header>
+
+ <nb-card-body>
+ <table class="table table-striped">
+ <tbody>
+ <tr>
+ <th>
+ <span class="fa fa-fw fa-id-badge"></span>&nbsp;
+ <span>Project ID</span>
+ </th>
+ <td>{{ project.id }}</td>
+ </tr>
+ <tr>
+ <th>
+ <span class="fa fa-fw fa-exchange"></span>&nbsp;
+ <span>Sharing type</span>
+ </th>
+ <td>{{ project.type | readableType }}</td>
+ </tr>
+ <tr>
+ <th>
+ <span class="fa fa-fw fa-folder-open-o"></span>&nbsp;
+ <span>Local path</span>
+ </th>
+ <td>{{ project.pathClient }}</td>
+ </tr>
+ <tr *ngIf="project.pathServer && project.pathServer != ''">
+ <th>
+ <span class="fa fa-fw fa-folder-open-o"></span>&nbsp;
+ <span>Server path</span>
+ </th>
+ <td>{{ project.pathServer }}</td>
+ </tr>
+ <tr>
+ <th>
+ <span class="fa fa-fw fa-flag"></span>&nbsp;
+ <span>Status</span>
+ </th>
+ <td>{{ project.status }} - {{ project.isInSync ? "Up to Date" : "Out of Sync"}}
+ <button *ngIf="!project.isInSync" class="btn btn-outline-info btn-tn btn-xds" (click)="sync(project)" style="margin-left:2em;">
+ <span class="fa fa-refresh fa-size-x2"></span>
+ </button>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </nb-card-body>
+
+ <nb-card-footer>
+ <!-- <pre>{{project | json}}</pre> -->
+ </nb-card-footer>
+</nb-card>
diff --git a/webapp/src/app/pages/projects/project-card/project-card.component.scss b/webapp/src/app/pages/projects/project-card/project-card.component.scss
new file mode 100644
index 0000000..a433f58
--- /dev/null
+++ b/webapp/src/app/pages/projects/project-card/project-card.component.scss
@@ -0,0 +1,54 @@
+@import '~@nebular/theme/styles/global/bootstrap/buttons';
+
+.xds-project-card .icon {
+ padding: 0.75rem 0;
+ font-size: 1.75rem;
+}
+
+nb-card-body {
+ padding: 0;
+}
+
+nb-card-footer {
+ text-align: right;
+}
+
+.fa-big {
+ font-size: 20px;
+ font-weight: bold;
+}
+
+.fa-size-x2 {
+ font-size: 20px;
+}
+
+th span {
+ font-weight: 100;
+}
+
+th label {
+ font-weight: 100;
+ margin-bottom: 0;
+}
+
+tr.info>th {
+ vertical-align: middle;
+}
+
+tr.info>td {
+ vertical-align: middle;
+}
+
+.btn-outline-danger.btn-xds {
+ color: #ff4c6a;
+ &:focus {
+ color: white;
+ }
+}
+
+.btn-outline-info.btn-xds {
+ color: #4ca6ff;
+ &:focus {
+ color: white;
+ }
+}
diff --git a/webapp/src/app/pages/projects/project-card/project-card.component.ts b/webapp/src/app/pages/projects/project-card/project-card.component.ts
new file mode 100644
index 0000000..160c4c8
--- /dev/null
+++ b/webapp/src/app/pages/projects/project-card/project-card.component.ts
@@ -0,0 +1,52 @@
+import { Component, Input, Pipe, PipeTransform } from '@angular/core';
+import { ProjectService, IProject, ProjectType, ProjectTypeEnum } from '../../../@core-xds/services/project.service';
+import { AlertService } from '../../../@core-xds/services/alert.service';
+
+
+@Component({
+ selector: 'xds-project-card',
+ styleUrls: ['./project-card.component.scss'],
+ templateUrl: './project-card.component.html',
+})
+export class ProjectCardComponent {
+
+ // FIXME workaround of https://github.com/angular/angular-cli/issues/2034
+ // should be removed with angular 5
+ // @Input() project: IProject;
+ @Input() project = <IProject>null;
+
+ constructor(
+ private alert: AlertService,
+ private projectSvr: ProjectService
+ ) {
+ }
+
+ delete(prj: IProject) {
+ this.projectSvr.Delete(prj).subscribe(
+ res => { },
+ err => this.alert.error('ERROR delete: ' + err)
+ );
+ }
+
+ sync(prj: IProject) {
+ this.projectSvr.Sync(prj).subscribe(
+ res => { },
+ err => this.alert.error('ERROR: ' + err)
+ );
+ }
+}
+
+// Make Project type human readable
+@Pipe({
+ name: 'readableType'
+})
+
+export class ProjectReadableTypePipe implements PipeTransform {
+ transform(type: ProjectTypeEnum): string {
+ switch (type) {
+ case ProjectType.NATIVE_PATHMAP: return 'Native (path mapping)';
+ case ProjectType.SYNCTHING: return 'Cloud (Syncthing)';
+ default: return String(type);
+ }
+ }
+}
diff --git a/webapp/src/app/pages/projects/projects.component.html b/webapp/src/app/pages/projects/projects.component.html
new file mode 100644
index 0000000..662dfcc
--- /dev/null
+++ b/webapp/src/app/pages/projects/projects.component.html
@@ -0,0 +1,26 @@
+<div class="row">
+ <div class="col-12">
+ <nb-card-body>
+ <div class="col-9">
+ <nb-actions size="medium">
+ <nb-action>
+ <button (click)="add()">
+ <i class="nb-plus"></i>
+ <span>Add Project</span>
+ </button>
+ </nb-action>
+ </nb-actions>
+ </div>
+ <div class="col-3 right">
+ <nb-actions size="medium">
+ <nb-action>
+ <nb-search type="rotate-layout"></nb-search>
+ </nb-action>
+ </nb-actions>
+ </div>
+ </nb-card-body>
+ </div>
+ <div class="col-md-12 col-lg-12 col-xxxl-6" *ngFor="let prj of (projects$ | async)">
+ <xds-project-card [project]="prj"></xds-project-card>
+ </div>
+</div>
diff --git a/webapp/src/app/pages/projects/projects.component.scss b/webapp/src/app/pages/projects/projects.component.scss
new file mode 100644
index 0000000..3631fbb
--- /dev/null
+++ b/webapp/src/app/pages/projects/projects.component.scss
@@ -0,0 +1,83 @@
+@import '../../@theme/styles/themes';
+@import '~@nebular/theme/components/card/card.component.theme';
+@import '~bootstrap/scss/mixins/breakpoints';
+@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
+@include nb-install-component() {
+ nb-card-body {
+ display: flex;
+ align-items: center;
+ }
+ .action-groups-header {
+ flex-basis: 20%;
+ color: nb-theme(card-header-fg-heading);
+ font-family: nb-theme(card-header-font-family);
+ font-size: nb-theme(card-header-font-size);
+ font-weight: nb-theme(card-header-font-weight);
+ }
+ .nb-actions {
+ flex-basis: 80%;
+ }
+ .right {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+ order: 1;
+ flex-direction: row-reverse;
+ }
+ nb-actions>nb-action {
+ padding: 0;
+ }
+ nb-action {
+ i {
+ color: nb-theme(color-fg);
+ font-size: 2.5rem;
+ margin-right: 1rem;
+ }
+ span {
+ font-family: nb-theme(font-secondary);
+ font-weight: nb-theme(font-weight-bold);
+ color: nb-theme(color-fg-heading);
+ text-transform: uppercase;
+ }
+ button {
+ margin: 0 auto;
+ padding: 0;
+ cursor: pointer;
+ border: none;
+ background: none;
+ display: flex;
+ align-items: center;
+ &:focus {
+ box-shadow: none;
+ outline: none;
+ }
+ }
+ }
+ @include media-breakpoint-down(md) {
+ nb-actions nb-action {
+ padding: 0 0.75rem;
+ }
+ }
+ @include media-breakpoint-down(sm) {
+ nb-card-body {
+ padding: 1rem;
+ }
+ nb-action {
+ font-size: 0.75rem;
+ i {
+ font-size: 2rem;
+ margin-right: 0.5rem;
+ }
+ }
+ }
+ @include media-breakpoint-down(is) {
+ nb-action i {
+ font-size: 1.75rem;
+ margin: 0;
+ }
+ span {
+ display: none;
+ }
+ }
+}
diff --git a/webapp/src/app/pages/projects/projects.component.ts b/webapp/src/app/pages/projects/projects.component.ts
new file mode 100644
index 0000000..179bbd0
--- /dev/null
+++ b/webapp/src/app/pages/projects/projects.component.ts
@@ -0,0 +1,33 @@
+import { Component, OnInit } from '@angular/core';
+import { Observable } from 'rxjs/Observable';
+
+import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
+import { ProjectAddModalComponent } from './project-add-modal/project-add-modal.component';
+
+import { ProjectService, IProject } from '../../@core-xds/services/project.service';
+
+@Component({
+ selector: 'xds-projects',
+ styleUrls: ['./projects.component.scss'],
+ templateUrl: './projects.component.html',
+})
+export class ProjectsComponent implements OnInit {
+
+ projects$: Observable<IProject[]>;
+ projects: IProject[];
+
+ constructor(
+ private projectSvr: ProjectService,
+ private modalService: NgbModal,
+ ) {
+ }
+
+ ngOnInit() {
+ this.projects$ = this.projectSvr.Projects$;
+ }
+
+ add() {
+ const activeModal = this.modalService.open(ProjectAddModalComponent, { size: 'lg', container: 'nb-layout' });
+ activeModal.componentInstance.modalHeader = 'Large Modal';
+ }
+}
diff --git a/webapp/src/app/pages/projects/projects.module.ts b/webapp/src/app/pages/projects/projects.module.ts
new file mode 100644
index 0000000..48f37ce
--- /dev/null
+++ b/webapp/src/app/pages/projects/projects.module.ts
@@ -0,0 +1,23 @@
+import { NgModule } from '@angular/core';
+import { ThemeModule } from '../../@theme/theme.module';
+
+import { ProjectsComponent } from './projects.component';
+import { ProjectCardComponent, ProjectReadableTypePipe } from './project-card/project-card.component';
+import { ProjectAddModalComponent } from './project-add-modal/project-add-modal.component';
+
+
+@NgModule({
+ imports: [
+ ThemeModule,
+ ],
+ declarations: [
+ ProjectsComponent,
+ ProjectCardComponent,
+ ProjectAddModalComponent,
+ ProjectReadableTypePipe,
+ ],
+ entryComponents: [
+ ProjectAddModalComponent
+ ],
+})
+export class ProjectsModule { }
diff --git a/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.html b/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.html
new file mode 100644
index 0000000..0c2787c
--- /dev/null
+++ b/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.html
@@ -0,0 +1,48 @@
+<nb-card class="xds-sdks">
+ <nb-card-header>
+
+ <div class="row">
+ <div class="col-12 col-md-8">
+ {{ labelGet(sdk) }}
+ </div>
+ <div class="col-6 col-md-4 text-right" role="group">
+ <button class="btn btn-outline-danger btn-tn btn-xds" (click)="delete(sdk)">
+ <span class="fa fa-trash fa-size-x2"></span>
+ </button>
+ </div>
+ </div>
+ </nb-card-header>
+
+ <nb-card-body>
+ <table class="table table-striped">
+ <tbody>
+ <tr>
+ <th>
+ <span class="fa fa-fw fa-id-badge"></span>&nbsp;
+ <span>SDK ID</span>
+ </th>
+ <td>{{ sdk.id }}</td>
+ </tr>
+ <tr>
+ <th><span class="fa fa-fw fa-user"></span>&nbsp;<span>Profile</span></th>
+ <td>{{ sdk.profile }}</td>
+ </tr> <tr>
+ <th><span class="fa fa-fw fa-tasks"></span>&nbsp;<span>Architecture</span></th>
+ <td>{{ sdk.arch }}</td>
+ </tr>
+ <tr>
+ <th><span class="fa fa-fw fa-code-fork"></span>&nbsp;<span>Version</span></th>
+ <td>{{ sdk.version }}</td>
+ </tr>
+ <tr>
+ <th><span class="fa fa-fw fa-folder-open-o"></span>&nbsp;<span>Sdk path</span></th>
+ <td>{{ sdk.path}}</td>
+ </tr>
+ </tbody>
+ </table>
+ </nb-card-body>
+
+ <nb-card-footer>
+ <!-- <pre>{{sdk | json}}</pre> -->
+ </nb-card-footer>
+</nb-card>
diff --git a/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.scss b/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.scss
new file mode 100644
index 0000000..e404610
--- /dev/null
+++ b/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.scss
@@ -0,0 +1,47 @@
+@import '~@nebular/theme/styles/global/bootstrap/buttons';
+
+.xds-sdk-card .icon {
+ padding: 0.75rem 0;
+ font-size: 1.75rem;
+}
+
+nb-card-body {
+ padding: 0;
+}
+
+nb-card-footer {
+ text-align: right;
+}
+
+.fa-big {
+ font-size: 20px;
+ font-weight: bold;
+}
+
+.fa-size-x2 {
+ font-size: 20px;
+}
+
+th span {
+ font-weight: 100;
+}
+
+th label {
+ font-weight: 100;
+ margin-bottom: 0;
+}
+
+tr.info>th {
+ vertical-align: middle;
+}
+
+tr.info>td {
+ vertical-align: middle;
+}
+
+.btn-outline-danger.btn-xds {
+ color: #ff4c6a;
+ &:focus {
+ color: white;
+ }
+}
diff --git a/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.ts b/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.ts
new file mode 100644
index 0000000..8228570
--- /dev/null
+++ b/webapp/src/app/pages/sdks/sdk-card/sdk-card.component.ts
@@ -0,0 +1,35 @@
+import { Component, Input, Pipe, PipeTransform } from '@angular/core';
+import { SdkService, ISdk } from '../../../@core-xds/services/sdk.service';
+import { AlertService } from '../../../@core-xds/services/alert.service';
+
+
+@Component({
+ selector: 'xds-sdk-card',
+ styleUrls: ['./sdk-card.component.scss'],
+ templateUrl: './sdk-card.component.html',
+})
+export class SdkCardComponent {
+
+ // FIXME workaround of https://github.com/angular/angular-cli/issues/2034
+ // should be removed with angular 5
+ // @Input() sdk: ISdk;
+ @Input() sdk = <ISdk>null;
+
+ constructor(
+ private alert: AlertService,
+ private sdkSvr: SdkService
+ ) {
+ }
+
+ labelGet(sdk: ISdk) {
+ return sdk.profile + '-' + sdk.arch + '-' + sdk.version;
+ }
+
+ delete(sdk: ISdk) {
+ this.sdkSvr.delete(sdk).subscribe(
+ res => { },
+ err => this.alert.error('ERROR delete: ' + err)
+ );
+ }
+}
+
diff --git a/webapp/src/app/pages/sdks/sdks.component.html b/webapp/src/app/pages/sdks/sdks.component.html
new file mode 100644
index 0000000..adfd924
--- /dev/null
+++ b/webapp/src/app/pages/sdks/sdks.component.html
@@ -0,0 +1,26 @@
+<div class="row">
+ <div class="col-12">
+ <nb-card-body>
+ <div class="col-9">
+ <nb-actions size="medium">
+ <nb-action>
+ <button (click)="add()">
+ <i class="nb-plus"></i>
+ <span>Add new SDK</span>
+ </button>
+ </nb-action>
+ </nb-actions>
+ </div>
+ <div class="col-3 right">
+ <nb-actions size="medium">
+ <nb-action>
+ <nb-search type="rotate-layout"></nb-search>
+ </nb-action>
+ </nb-actions>
+ </div>
+ </nb-card-body>
+ </div>
+ <div class="col-md-12 col-lg-12 col-xxxl-6" *ngFor="let sdk of (sdks$ | async)">
+ <xds-sdk-card [sdk]="sdk"></xds-sdk-card>
+ </div>
+</div>
diff --git a/webapp/src/app/pages/sdks/sdks.component.scss b/webapp/src/app/pages/sdks/sdks.component.scss
new file mode 100644
index 0000000..3631fbb
--- /dev/null
+++ b/webapp/src/app/pages/sdks/sdks.component.scss
@@ -0,0 +1,83 @@
+@import '../../@theme/styles/themes';
+@import '~@nebular/theme/components/card/card.component.theme';
+@import '~bootstrap/scss/mixins/breakpoints';
+@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
+@include nb-install-component() {
+ nb-card-body {
+ display: flex;
+ align-items: center;
+ }
+ .action-groups-header {
+ flex-basis: 20%;
+ color: nb-theme(card-header-fg-heading);
+ font-family: nb-theme(card-header-font-family);
+ font-size: nb-theme(card-header-font-size);
+ font-weight: nb-theme(card-header-font-weight);
+ }
+ .nb-actions {
+ flex-basis: 80%;
+ }
+ .right {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+ order: 1;
+ flex-direction: row-reverse;
+ }
+ nb-actions>nb-action {
+ padding: 0;
+ }
+ nb-action {
+ i {
+ color: nb-theme(color-fg);
+ font-size: 2.5rem;
+ margin-right: 1rem;
+ }
+ span {
+ font-family: nb-theme(font-secondary);
+ font-weight: nb-theme(font-weight-bold);
+ color: nb-theme(color-fg-heading);
+ text-transform: uppercase;
+ }
+ button {
+ margin: 0 auto;
+ padding: 0;
+ cursor: pointer;
+ border: none;
+ background: none;
+ display: flex;
+ align-items: center;
+ &:focus {
+ box-shadow: none;
+ outline: none;
+ }
+ }
+ }
+ @include media-breakpoint-down(md) {
+ nb-actions nb-action {
+ padding: 0 0.75rem;
+ }
+ }
+ @include media-breakpoint-down(sm) {
+ nb-card-body {
+ padding: 1rem;
+ }
+ nb-action {
+ font-size: 0.75rem;
+ i {
+ font-size: 2rem;
+ margin-right: 0.5rem;
+ }
+ }
+ }
+ @include media-breakpoint-down(is) {
+ nb-action i {
+ font-size: 1.75rem;
+ margin: 0;
+ }
+ span {
+ display: none;
+ }
+ }
+}
diff --git a/webapp/src/app/pages/sdks/sdks.component.ts b/webapp/src/app/pages/sdks/sdks.component.ts
new file mode 100644
index 0000000..3208121
--- /dev/null
+++ b/webapp/src/app/pages/sdks/sdks.component.ts
@@ -0,0 +1,35 @@
+import { Component, OnInit } from '@angular/core';
+import { Observable } from 'rxjs/Observable';
+
+import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
+//import { SdkAddModalComponent } from './sdk-add-modal/sdk-add-modal.component';
+
+import { SdkService, ISdk } from '../../@core-xds/services/sdk.service';
+
+@Component({
+ selector: 'xds-sdks',
+ styleUrls: ['./sdks.component.scss'],
+ templateUrl: './sdks.component.html',
+})
+export class SdksComponent implements OnInit {
+
+ sdks$: Observable<ISdk[]>;
+ sdks: ISdk[];
+
+ constructor(
+ private sdkSvr: SdkService,
+ private modalService: NgbModal,
+ ) {
+ }
+
+ ngOnInit() {
+ this.sdks$ = this.sdkSvr.Sdks$;
+ }
+
+ add() {
+ /* SEB TODO
+ const activeModal = this.modalService.open(SdkAddModalComponent, { size: 'lg', container: 'nb-layout' });
+ activeModal.componentInstance.modalHeader = 'Large Modal';
+ */
+ }
+}
diff --git a/webapp/src/app/pages/sdks/sdks.module.ts b/webapp/src/app/pages/sdks/sdks.module.ts
new file mode 100644
index 0000000..48629f6
--- /dev/null
+++ b/webapp/src/app/pages/sdks/sdks.module.ts
@@ -0,0 +1,22 @@
+import { NgModule } from '@angular/core';
+import { ThemeModule } from '../../@theme/theme.module';
+
+import { SdksComponent } from './sdks.component';
+import { SdkCardComponent } from './sdk-card/sdk-card.component';
+//import { SdkAddModalComponent } from './sdk-add-modal/sdk-add-modal.component';
+
+
+@NgModule({
+ imports: [
+ ThemeModule,
+ ],
+ declarations: [
+ SdksComponent,
+ SdkCardComponent,
+ //SdkAddModalComponent,
+ ],
+ entryComponents: [
+ //SdkAddModalComponent
+ ],
+})
+export class SdksModule { }
diff --git a/webapp/src/app/projects/projectAddModal.component.css b/webapp/src/app/projects/projectAddModal.component.css
deleted file mode 100644
index 77f73a5..0000000
--- a/webapp/src/app/projects/projectAddModal.component.css
+++ /dev/null
@@ -1,24 +0,0 @@
-.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
deleted file mode 100644
index dc84985..0000000
--- a/webapp/src/app/projects/projectAddModal.component.html
+++ /dev/null
@@ -1,54 +0,0 @@
-<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
deleted file mode 100644
index 0718f4c..0000000
--- a/webapp/src/app/projects/projectAddModal.component.ts
+++ /dev/null
@@ -1,147 +0,0 @@
-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 { ProjectService, IProject, ProjectType, ProjectTypes } from '../services/project.service';
-
-
-@Component({
- selector: 'xds-project-add-modal',
- templateUrl: 'projectAddModal.component.html',
- styleUrls: ['projectAddModal.component.css']
-})
-export class ProjectAddModalComponent implements OnInit {
- @ViewChild('childProjectModal') public childProjectModal: ModalDirective;
- @Input() title?: string;
- @Input('server-id') serverID: string;
-
- cancelAction = false;
- userEditedLabel = false;
- projectTypes = ProjectTypes;
-
- addProjectForm: FormGroup;
- typeCtrl: FormControl;
- pathCliCtrl: FormControl;
- pathSvrCtrl: FormControl;
-
- constructor(
- private alert: AlertService,
- private projectSvr: ProjectService,
- private fb: FormBuilder
- ) {
- // Define types (first one is special/placeholder)
- this.projectTypes.unshift({ value: ProjectType.UNSET, display: '--Select a type--' });
-
- this.typeCtrl = new FormControl(this.projectTypes[0].value, Validators.pattern('[A-Za-z]+'));
- 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() {
- // Auto create label name
- this.pathCliCtrl.valueChanges
- .debounceTime(100)
- .filter(n => n)
- .map(n => {
- const last = n.split('/');
- let nm = n;
- if (last.length > 0) {
- nm = last.pop();
- if (nm === '' && last.length > 0) {
- nm = last.pop();
- }
- }
- return 'Project_' + nm;
- })
- .subscribe(value => {
- if (value && !this.userEditedLabel) {
- this.addProjectForm.patchValue({ label: value });
- }
- });
-
- // Handle disabling of Server path
- this.typeCtrl.valueChanges
- .debounceTime(500)
- .subscribe(valType => {
- const dis = (valType === String(ProjectType.SYNCTHING));
- this.pathSvrCtrl.reset({ value: '', disabled: dis });
- });
- }
-
- show() {
- this.cancelAction = false;
- this.userEditedLabel = 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;
- }
-
- const formVal = this.addProjectForm.value;
-
- const type = formVal['type'].value;
- this.projectSvr.Add({
- serverId: this.serverID,
- label: formVal['label'],
- pathClient: formVal['pathCli'],
- pathServer: formVal['pathSvr'],
- type: formVal['type'],
- // FIXME: allow to set defaultSdkID from New Project config panel
- })
- .subscribe(prj => {
- this.alert.info('Project ' + prj.label + ' successfully created.');
- this.hide();
-
- // Reset Value for the next creation
- this.addProjectForm.reset();
- const selectedType = this.projectTypes[0].value;
- this.addProjectForm.patchValue({ type: selectedType });
-
- },
- err => {
- this.alert.error(err, 60);
- this.hide();
- });
- }
-
-}
diff --git a/webapp/src/app/projects/projectCard.component.ts b/webapp/src/app/projects/projectCard.component.ts
deleted file mode 100644
index a28b96c..0000000
--- a/webapp/src/app/projects/projectCard.component.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-import { Component, Input, Pipe, PipeTransform } from '@angular/core';
-import { ProjectService, IProject, ProjectType } from '../services/project.service';
-import { AlertService } from '../services/alert.service';
-
-@Component({
- selector: 'xds-project-card',
- template: `
- <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>
- </div>
- </div>
- </div>
-
- <table class="table table-striped">
- <tbody>
- <tr>
- <th><span class="fa fa-fw fa-id-badge"></span>&nbsp;<span>Project ID</span></th>
- <td>{{ project.id }}</td>
- </tr>
- <tr>
- <th><span class="fa fa-fw fa-exchange"></span>&nbsp;<span>Sharing type</span></th>
- <td>{{ project.type | readableType }}</td>
- </tr>
- <tr>
- <th><span class="fa fa-fw fa-folder-open-o"></span>&nbsp;<span>Local path</span></th>
- <td>{{ project.pathClient }}</td>
- </tr>
- <tr *ngIf="project.pathServer && project.pathServer != ''">
- <th><span class="fa fa-fw fa-folder-open-o"></span>&nbsp;<span>Server path</span></th>
- <td>{{ project.pathServer }}</td>
- </tr>
- <tr>
- <th><span class="fa fa-fw fa-flag"></span>&nbsp;<span>Status</span></th>
- <td>{{ project.status }} - {{ project.isInSync ? "Up to Date" : "Out of Sync"}}
- <button *ngIf="!project.isInSync" class="btn btn-link" (click)="sync(project)">
- <span class="fa fa-refresh fa-size-x2"></span>
- </button>
- </td>
- </tr>
- </tbody>
- </table >
- `,
- styleUrls: ['../config/config.component.css']
-})
-
-export class ProjectCardComponent {
-
- @Input() project: IProject;
-
- constructor(
- private alert: AlertService,
- private projectSvr: ProjectService
- ) {
- }
-
- delete(prj: IProject) {
- this.projectSvr.Delete(prj)
- .subscribe(res => {
- }, err => {
- this.alert.error('Delete ERROR: ' + err);
- });
- }
-
- sync(prj: IProject) {
- this.projectSvr.Sync(prj)
- .subscribe(res => {
- }, err => {
- this.alert.error('ERROR: ' + err);
- });
- }
-
-}
-
-// Remove APPS. prefix if translate has failed
-@Pipe({
- name: 'readableType'
-})
-
-export class ProjectReadableTypePipe implements PipeTransform {
- transform(type: ProjectType): string {
- switch (type) {
- case ProjectType.NATIVE_PATHMAP: return 'Native (path mapping)';
- case ProjectType.SYNCTHING: return 'Cloud (Syncthing)';
- default: return String(type);
- }
- }
-}
diff --git a/webapp/src/app/projects/projectsListAccordion.component.ts b/webapp/src/app/projects/projectsListAccordion.component.ts
deleted file mode 100644
index 0dd2f12..0000000
--- a/webapp/src/app/projects/projectsListAccordion.component.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { Component, Input } from '@angular/core';
-
-import { IProject } from '../services/project.service';
-
-@Component({
- selector: 'xds-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 }}
- <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>
- <xds-project-card [project]="prj"></xds-project-card>
- </accordion-group>
- </accordion>
- `
-})
-export class ProjectsListAccordionComponent {
-
- @Input() projects: IProject[];
-
-}
-
-
diff --git a/webapp/src/app/sdks/sdkAddModal.component.html b/webapp/src/app/sdks/sdkAddModal.component.html
deleted file mode 100644
index 44c667e..0000000
--- a/webapp/src/app/sdks/sdkAddModal.component.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<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)="hide()">
- <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
deleted file mode 100644
index 064f02f..0000000
--- a/webapp/src/app/sdks/sdkAddModal.component.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { Component, Input, ViewChild } from '@angular/core';
-import { ModalDirective } from 'ngx-bootstrap/modal';
-
-@Component({
- selector: 'xds-sdk-add-modal',
- templateUrl: '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/sdks/sdkCard.component.ts b/webapp/src/app/sdks/sdkCard.component.ts
deleted file mode 100644
index b277887..0000000
--- a/webapp/src/app/sdks/sdkCard.component.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import { Component, Input } from '@angular/core';
-import { ISdk } from '../services/sdk.service';
-
-@Component({
- selector: 'xds-sdk-card',
- template: `
- <div class="row">
- <div class="col-xs-12">
- <div class="text-right" role="group">
- <button disabled class="btn btn-link" (click)="delete(sdk)"><span class="fa fa-trash fa-size-x2"></span></button>
- </div>
- </div>
- </div>
-
- <table class="table table-striped">
- <tbody>
- <tr>
- <th><span class="fa fa-fw fa-id-badge"></span>&nbsp;<span>SDK ID</span></th>
- <td>{{ sdk.id }}</td>
- </tr>
- <tr>
- <th><span class="fa fa-fw fa-user"></span>&nbsp;<span>Profile</span></th>
- <td>{{ sdk.profile }}</td>
- </tr>
- <tr>
- <th><span class="fa fa-fw fa-tasks"></span>&nbsp;<span>Architecture</span></th>
- <td>{{ sdk.arch }}</td>
- </tr>
- <tr>
- <th><span class="fa fa-fw fa-code-fork"></span>&nbsp;<span>Version</span></th>
- <td>{{ sdk.version }}</td>
- </tr>
- <tr>
- <th><span class="fa fa-fw fa-folder-open-o"></span>&nbsp;<span>Sdk path</span></th>
- <td>{{ sdk.path}}</td>
- </tr>
-
- </tbody>
- </table >
- `,
- styleUrls: ['../config/config.component.css']
-})
-
-export class SdkCardComponent {
-
- @Input() sdk: ISdk;
-
- constructor() { }
-
-
- delete(sdk: ISdk) {
- // Not supported for now
- }
-
-}
diff --git a/webapp/src/app/sdks/sdksListAccordion.component.ts b/webapp/src/app/sdks/sdksListAccordion.component.ts
deleted file mode 100644
index 73ad182..0000000
--- a/webapp/src/app/sdks/sdksListAccordion.component.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Component, Input } from '@angular/core';
-
-import { ISdk } from '../services/sdk.service';
-
-@Component({
- selector: 'xds-sdks-list-accordion',
- template: `
- <accordion>
- <accordion-group #group *ngFor="let sdk of sdks">
- <div accordion-heading>
- {{ sdk.name }}
- <i class="pull-right float-xs-right fa"
- [ngClass]="{'fa-chevron-down': group.isOpen, 'fa-chevron-right': !group.isOpen}"></i>
- </div>
- <xds-sdk-card [sdk]="sdk"></xds-sdk-card>
- </accordion-group>
- </accordion>
- `
-})
-export class SdksListAccordionComponent {
-
- @Input() sdks: ISdk[];
-
-}
-
-
diff --git a/webapp/src/app/services/alert.service.ts b/webapp/src/app/services/alert.service.ts
deleted file mode 100644
index aee5827..0000000
--- a/webapp/src/app/services/alert.service.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import { Injectable, SecurityContext } from '@angular/core';
-import { DomSanitizer } from '@angular/platform-browser';
-import { Observable } from 'rxjs/Observable';
-import { Subject } from 'rxjs/Subject';
-
-
-export type AlertType = 'danger' | '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(private sanitizer: DomSanitizer) {
- this.alerts = this.alertsSubject.asObservable();
- this._alerts = [];
- this.uid = 0;
- }
-
- public error(msg: string, dismissTime?: number) {
- this.add({
- type: 'danger', 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({
- show: true,
- type: al.type,
- msg: this.sanitizer.sanitize(SecurityContext.HTML, 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);
- }
- }
-}
diff --git a/webapp/src/app/services/project.service.ts b/webapp/src/app/services/project.service.ts
deleted file mode 100644
index 61d8f1c..0000000
--- a/webapp/src/app/services/project.service.ts
+++ /dev/null
@@ -1,199 +0,0 @@
-import { Injectable, SecurityContext } from '@angular/core';
-import { Observable } from 'rxjs/Observable';
-import { BehaviorSubject } from 'rxjs/BehaviorSubject';
-
-import { XDSAgentService, IXDSProjectConfig } from '../services/xdsagent.service';
-
-export enum 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: ProjectType;
- 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/services/utils.service.ts b/webapp/src/app/services/utils.service.ts
deleted file mode 100644
index e665e2a..0000000
--- a/webapp/src/app/services/utils.service.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { Injectable } from '@angular/core';
-
-@Injectable()
-export class UtilsService {
- constructor() { }
-
- getOSName(lowerCase?: boolean): string {
- const checkField = function (ff) {
- if (ff.indexOf('Linux') !== -1) {
- return 'Linux';
- } else if (ff.indexOf('Win') !== -1) {
- return 'Windows';
- } else if (ff.indexOf('Mac') !== -1) {
- return 'MacOS';
- } else if (ff.indexOf('X11') !== -1) {
- return 'UNIX';
- }
- return '';
- };
-
- let OSName = checkField(navigator.platform);
- if (OSName === '') {
- OSName = checkField(navigator.appVersion);
- }
- if (OSName === '') {
- OSName = 'Unknown OS';
- }
- if (lowerCase) {
- return OSName.toLowerCase();
- }
- return OSName;
- }
-}
diff --git a/webapp/src/app/services/xdsagent.service.ts b/webapp/src/app/services/xdsagent.service.ts
deleted file mode 100644
index 55653c7..0000000
--- a/webapp/src/app/services/xdsagent.service.ts
+++ /dev/null
@@ -1,384 +0,0 @@
-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 } 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: ProjectType;
- 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 IAgentStatus {
- WS_connected: boolean;
-}
-
-
-@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 = { WS_connected: false };
-
- 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 _WSState(sts: boolean) {
- this._status.WS_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.configSubject.next(
- Object.assign({ servers: [] }, this._config)
- );
- });
- }
- }
-
- private _handleIoSocket() {
- this.socket = io(this.wsUrl, { transports: ['websocket'] });
-
- this.socket.on('connect_error', (res) => {
- this._WSState(false);
- console.error('XDS Agent WebSocket Connection error !');
- });
-
- 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);
- });
-
- 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.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, r: number) {
- const svr = this._getServer(serverID);
- if (!svr) {
- return Observable.of([]);
- }
-
- svr.connRetry = r;
- this.setConfig(this._config).subscribe(
- newCfg => {
- this._config = newCfg;
- this.configSubject.next(Object.assign({}, this._config));
- },
- err => {
- this.alert.error(err);
- }
- );
- }
-
- setServerUrl(serverID: string, url: string) {
- const svr = this._getServer(serverID);
- if (!svr) {
- return Observable.of([]);
- }
- svr.url = url;
- this.setConfig(this._config).subscribe(
- newCfg => {
- this._config = newCfg;
- this.configSubject.next(Object.assign({}, this._config));
- },
- err => {
- this.alert.error(err);
- }
- );
- }
-
- /***
- ** 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);
- }
-}