Merge pull request #276 from GNS3/dependencies-list

Installation of external software on Windows, Ref. #251, #258
This commit is contained in:
ziajka 2019-01-28 12:14:11 +01:00 committed by GitHub
commit b4d9b28392
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 748 additions and 42 deletions

3
.gitignore vendored
View File

@ -52,3 +52,6 @@ Thumbs.db
# Licenses file
licenses.csv
# Temps
.temp-var-file.ts

View File

@ -13,6 +13,7 @@ files:
- main.js
- renderer.js
- sentry.js
- installed-software.js
- package.json
mac:

88
installed-software.js Normal file
View File

@ -0,0 +1,88 @@
var commandExistsSync = require('command-exists').sync;
var app = require('electron').app;
var fs = require('fs');
var util = require('util');
var fetch = require('node-fetch')
var stream = require('stream');
var path = require('path');
const { spawn } = require('child_process');
const { ipcMain } = require('electron')
var pipeline = util.promisify(stream.pipeline);
exports.getInstalledSoftware = (softwareList) => {
const installed = {};
for(var software of softwareList) {
var name = software.name;
var locations = software.locations;
installed[name] = [];
for(var location of locations) {
// var exists = commandExistsSync(command);
var exists = fs.existsSync(location);
if(exists) {
installed[name].push(location);
}
}
}
return installed;
}
async function downloadFile(resource, softwarePath) {
var response = await fetch(resource);
if (response.status != 200) {
throw new Error(`Cannot download file ${resource}, response status = ${response.status}`);
}
await pipeline(
response.body,
fs.createWriteStream(softwarePath)
);
}
ipcMain.on('installed-software-install', async function (event, software) {
const softwarePath = path.join(app.getPath('temp'), software.binary);
const responseChannel = `installed-software-installed-${software.name}`;
if (software.type == 'web') {
const exists = fs.existsSync(softwarePath);
if (exists) {
console.log(`Skipping downloading file due to '${softwarePath}' path exists`);
}
else {
console.log(`File '${softwarePath}' doesn't exist. Downloading file.`);
try {
await downloadFile(software.resource, softwarePath);
} catch(error) {
event.sender.send(responseChannel, {
success: false,
message: error.message
});
}
}
}
let child;
if (software.sudo) {
child = spawn('powershell.exe', ['Start-Process', '-FilePath', `"${softwarePath}"`]);
}
else {
child = spawn(softwarePath, software.installation_arguments);
}
child.on('exit', () => {
event.sender.send(responseChannel, {
success: true
});
});
child.on('error', (err) => {
event.sender.send(responseChannel, {
success: false,
message: err.message
});
});
child.stdin.end();
});

View File

@ -6,6 +6,8 @@ const path = require('path');
const url = require('url');
const yargs = require('yargs');
const { ipcMain } = require('electron')
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;
@ -138,7 +140,6 @@ app.on('activate', function () {
}
});
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

View File

@ -32,7 +32,9 @@
"prettier:base": "prettier",
"prettier:check": "yarn prettier:base -- --list-different \"src/**/*.{ts,js,html,scss}\"",
"prettier:write": "yarn prettier:base -- --write \"src/**/*.{ts,js,html,scss}\"",
"generate-licenses-file": "yarn license-checker --production --csv --out licenses.csv"
"generate-licenses-file": "yarn license-checker --production --csv --out licenses.csv",
"prebuild": "node set-variables-in-env.js --set src/environments/environment.prod.ts",
"postbuild": "node set-variables-in-env.js --unset src/environments/environment.prod.ts"
},
"private": true,
"dependencies": {
@ -51,6 +53,7 @@
"angular2-hotkeys": "^2.1.4",
"angular2-indexeddb": "^1.2.3",
"bootstrap": "4.2.1",
"command-exists": "^1.2.8",
"core-js": "^2.6.1",
"css-tree": "^1.0.0-alpha.29",
"d3-ng2-service": "^2.1.0",
@ -88,10 +91,12 @@
"karma-jasmine": "~2.0.1",
"karma-jasmine-html-reporter": "^1.4.0",
"license-checker": "^24.1.0",
"node-fetch": "^2.3.0",
"node-sass": "^4.11.0",
"popper.js": "^1.14.6",
"prettier": "^1.15.2",
"protractor": "~5.4.2",
"replace": "^1.0.1",
"ts-mockito": "^2.3.1",
"ts-node": "~7.0.1",
"tslint": "~5.12.0",

21
set-variables-in-env.js Normal file
View File

@ -0,0 +1,21 @@
const yargs = require('yargs');
const fs = require('fs');
const argv = yargs.argv;
const tempFile = `.temp-var-file.ts`;
if(argv.set) {
const envFile = argv.set;
console.log(`Backuping up '${envFile}' into '${tempFile}'.`);
fs.copyFileSync(envFile, tempFile);
const content = fs.readFileSync(envFile, "utf8");
const variables = `solarputty_download_url: '${process.env.SOLARPUTTY_DOWNLOAD_URL}',`
const replaced = content.replace('//ENV', variables);
fs.writeFileSync(envFile, replaced);
}
if(argv.unset) {
const envFile = argv.unset;
console.log(`Restoring '${tempFile}' into '${envFile}'.`);
fs.copyFileSync(tempFile, envFile);
}

View File

@ -2,11 +2,13 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ProjectMapComponent } from './components/project-map/project-map.component';
import { ServersComponent } from './components/servers/servers.component';
import { ProjectsComponent } from './components/projects/projects.component';
import { DefaultLayoutComponent } from './layouts/default-layout/default-layout.component';
import { SettingsComponent } from './components/settings/settings.component';
import { LocalServerComponent } from './components/local-server/local-server.component';
import { ServersComponent } from "./components/servers/servers.component";
import { ProjectsComponent } from "./components/projects/projects.component";
import { DefaultLayoutComponent } from "./layouts/default-layout/default-layout.component";
import { SettingsComponent } from "./components/settings/settings.component";
import { LocalServerComponent } from "./components/local-server/local-server.component";
import { InstalledSoftwareComponent } from './components/installed-software/installed-software.component';
const routes: Routes = [
{
@ -17,7 +19,8 @@ const routes: Routes = [
{ path: 'servers', component: ServersComponent },
{ path: 'local', component: LocalServerComponent },
{ path: 'server/:server_id/projects', component: ProjectsComponent },
{ path: 'settings', component: SettingsComponent }
{ path: 'settings', component: SettingsComponent },
{ path: 'installed-software', component: InstalledSoftwareComponent },
]
},
{ path: 'server/:server_id/project/:project_id', component: ProjectMapComponent }

View File

@ -75,6 +75,8 @@ import { ProjectNameValidator } from './components/projects/models/projectNameVa
import { MatSidenavModule } from '@angular/material';
import { NodeSelectInterfaceComponent } from './components/project-map/node-select-interface/node-select-interface.component';
import { DrawLinkToolComponent } from './components/project-map/draw-link-tool/draw-link-tool.component';
import { InstalledSoftwareComponent } from './components/installed-software/installed-software.component';
import { DrawingResizedComponent } from './components/drawings-listeners/drawing-resized/drawing-resized.component';
import { TextEditedComponent } from './components/drawings-listeners/text-edited/text-edited.component';
import { NodeDraggedComponent } from './components/drawings-listeners/node-dragged/node-dragged.component';
@ -85,9 +87,14 @@ import { InterfaceLabelDraggedComponent } from './components/drawings-listeners/
import { ToolsService } from './services/tools.service';
import { TextAddedComponent } from './components/drawings-listeners/text-added/text-added.component';
import { DrawingAddedComponent } from './components/drawings-listeners/drawing-added/drawing-added.component';
import { InstallSoftwareComponent } from './components/installed-software/install-software/install-software.component';
import { StyleEditorDialogComponent } from './components/project-map/drawings-editors/style-editor/style-editor.component';
import { EditTextActionComponent } from './components/project-map/context-menu/actions/edit-text-action/edit-text-action.component';
import { TextEditorDialogComponent } from './components/project-map/drawings-editors/text-editor/text-editor.component';
import { InstalledSoftwareService } from './services/installed-software.service';
import { ExternalSoftwareDefinitionService } from './services/external-software-definition.service';
import { PlatformService } from './services/platform.service';
if (environment.production) {
Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', {
@ -129,6 +136,7 @@ if (environment.production) {
ServerDiscoveryComponent,
NodeSelectInterfaceComponent,
DrawLinkToolComponent,
InstalledSoftwareComponent,
DrawingAddedComponent,
DrawingResizedComponent,
TextAddedComponent,
@ -138,6 +146,7 @@ if (environment.production) {
DrawingDraggedComponent,
LinkCreatedComponent,
InterfaceLabelDraggedComponent,
InstallSoftwareComponent,
StyleEditorDialogComponent,
TextEditorDialogComponent
],
@ -185,7 +194,10 @@ if (environment.production) {
ServerErrorHandler,
ServerDatabase,
ProjectNameValidator,
ToolsService
ToolsService,
InstalledSoftwareService,
ExternalSoftwareDefinitionService,
PlatformService
],
entryComponents: [
AddServerDialogComponent,

View File

@ -0,0 +1,3 @@
<button mat-button color="primary" (click)="install()" [disabled]="disabled">
<ng-container *ngIf="readyToInstall">{{ buttonText }}</ng-container>
</button>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { InstallSoftwareComponent } from './install-software.component';
describe('InstallSoftwareComponent', () => {
let component: InstallSoftwareComponent;
let fixture: ComponentFixture<InstallSoftwareComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ InstallSoftwareComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(InstallSoftwareComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
// it('should create', () => {
// expect(component).toBeTruthy();
// });
});

View File

@ -0,0 +1,59 @@
import { Component, OnInit, Output, EventEmitter, Input, OnDestroy, OnChanges } from '@angular/core';
import { ElectronService } from 'ngx-electron';
@Component({
selector: 'app-install-software',
templateUrl: './install-software.component.html',
styleUrls: ['./install-software.component.scss']
})
export class InstallSoftwareComponent implements OnInit, OnDestroy, OnChanges {
@Input('software')
software: any;
@Output()
installedChanged = new EventEmitter();
public disabled = false;
public readyToInstall = true;
public buttonText: string;
constructor(
private electronService: ElectronService
) { }
ngOnInit() {
this.electronService.ipcRenderer.on(this.responseChannel, (event, data) => {
this.updateButton();
this.installedChanged.emit(data);
});
}
ngOnDestroy() {
this.electronService.ipcRenderer.removeAllListeners(this.responseChannel);
}
ngOnChanges() {
this.updateButton();
}
install() {
this.disabled = true;
this.buttonText = "Installing";
this.electronService.ipcRenderer.send('installed-software-install', this.software);
}
private get responseChannel() {
return `installed-software-installed-${this.software.name}`;
}
private updateButton() {
this.disabled = this.software.installed;
if (this.software.installed) {
this.buttonText = "Installed";
}
else {
this.buttonText = "Install";
}
}
}

View File

@ -0,0 +1,28 @@
<div class="content">
<div class="default-header">
<h1>Installed software</h1>
</div>
<div class="default-content">
<div class="example-container mat-elevation-z8">
<mat-table #table [dataSource]="dataSource">
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef> Name </mat-header-cell>
<mat-cell *matCellDef="let row;">{{ row.name }}</mat-cell>
</ng-container>
<ng-container matColumnDef="actions">
<mat-header-cell *matHeaderCellDef></mat-header-cell>
<mat-cell *matCellDef="let row;" style="text-align: right">
<app-install-software [software]="row" (installedChanged)="onInstalled($event)"></app-install-software>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>
</div>
</div>
</div>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { InstalledSoftwareComponent } from './installed-software.component';
describe('InstalledSoftwareComponent', () => {
let component: InstalledSoftwareComponent;
let fixture: ComponentFixture<InstalledSoftwareComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ InstalledSoftwareComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(InstalledSoftwareComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
// it('should create', () => {
// expect(component).toBeTruthy();
// });
});

View File

@ -0,0 +1,53 @@
import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
import { InstalledSoftwareService } from '../../services/installed-software.service';
import { DataSource } from '@angular/cdk/table';
import { Observable, of, BehaviorSubject } from 'rxjs';
@Component({
selector: 'app-installed-software',
templateUrl: './installed-software.component.html',
styleUrls: ['./installed-software.component.scss']
})
export class InstalledSoftwareComponent implements OnInit {
dataSource: InstalledSoftwareDataSource;
displayedColumns = ['name', 'actions'];
constructor(
private installedSoftwareService: InstalledSoftwareService,
private changeDetectorRef: ChangeDetectorRef
) { }
ngOnInit() {
this.dataSource = new InstalledSoftwareDataSource(this.installedSoftwareService);
}
onInstalled(event) {
this.dataSource.refresh();
/**
* During software installation we are not performing any user action
* in browser hence Angular doesn't know something suppose to change.
* Here we ask to detect changes manually.
*/
this.changeDetectorRef.detectChanges();
}
}
export class InstalledSoftwareDataSource extends DataSource<any> {
installed = new BehaviorSubject([]);
constructor(private installedSoftwareService: InstalledSoftwareService) {
super();
}
connect(): Observable<any[]> {
this.refresh();
return this.installed;
}
disconnect() {}
refresh() {
this.installed.next(this.installedSoftwareService.list());
}
}

View File

@ -3,7 +3,7 @@
<div class="default-content">
<div class="example-container mat-elevation-z8">
<mat-accordion>
<mat-expansion-panel>
<mat-expansion-panel [expanded]="true">
<mat-expansion-panel-header>
<mat-panel-title> Local settings </mat-panel-title>
<mat-panel-description> Customize your local settings </mat-panel-description>
@ -32,4 +32,5 @@
<div class="buttons-bar"><button mat-raised-button color="primary" (click)="save()">Save settings</button></div>
</div>
</div>

View File

@ -6,7 +6,22 @@
<span class="fill-space"></span>
<button mat-button routerLink="/settings"><mat-icon>settings</mat-icon></button>
<button mat-button [matMenuTriggerFor]="menu" >
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item routerLink="/settings">
<mat-icon>settings</mat-icon>
<span>Settings</span>
</button>
<button mat-menu-item routerLink="/installed-software" [disabled]="!isInstalledSoftwareAvailable">
<mat-icon>cloud_download</mat-icon>
<span>Installed software</span>
</button>
</mat-menu>
</mat-toolbar>
</header>
@ -14,4 +29,7 @@
<app-progress></app-progress>
<footer class="footer mat-app-background">GNS3 Web UI demo &copy; 2018</footer>
<footer class="footer mat-app-background">
GNS3 Web UI &copy; 2019
</footer>

View File

@ -1,14 +1,35 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DefaultLayoutComponent } from './default-layout.component';
import { ElectronService } from 'ngx-electron';
import { MatIconModule, MatMenuModule, MatToolbarModule, MatProgressSpinnerModule } from '@angular/material';
import { RouterTestingModule } from '@angular/router/testing';
import { ProgressComponent } from '../../common/progress/progress.component';
import { ProgressService } from '../../common/progress/progress.service';
class ElectronServiceMock {
public isElectronApp: boolean;
}
describe('DefaultLayoutComponent', () => {
let component: DefaultLayoutComponent;
let fixture: ComponentFixture<DefaultLayoutComponent>;
let electronServiceMock: ElectronServiceMock;
beforeEach(async(() => {
electronServiceMock = new ElectronServiceMock();
TestBed.configureTestingModule({
declarations: [DefaultLayoutComponent]
declarations: [DefaultLayoutComponent, ProgressComponent],
imports: [MatIconModule, MatMenuModule, MatToolbarModule, RouterTestingModule, MatProgressSpinnerModule],
providers: [
{
provide: ElectronService,
useValue: electronServiceMock
},
ProgressService
]
}).compileComponents();
}));
@ -18,7 +39,19 @@ describe('DefaultLayoutComponent', () => {
fixture.detectChanges();
});
// it('should create', () => {
// expect(component).toBeTruthy();
// });
it('should create', () => {
expect(component).toBeTruthy();
});
it('should installed software be available', () => {
electronServiceMock.isElectronApp = true;
component.ngOnInit();
expect(component.isInstalledSoftwareAvailable).toBeTruthy();
});
it('should installed software be not available', () => {
electronServiceMock.isElectronApp = false;
component.ngOnInit();
expect(component.isInstalledSoftwareAvailable).toBeFalsy();
});
});

View File

@ -1,3 +1,4 @@
import { ElectronService } from 'ngx-electron';
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
@Component({
@ -7,7 +8,14 @@ import { Component, OnInit, ViewEncapsulation } from '@angular/core';
styleUrls: ['./default-layout.component.css']
})
export class DefaultLayoutComponent implements OnInit {
constructor() {}
public isInstalledSoftwareAvailable = false;
constructor(
private electronService: ElectronService
) {}
ngOnInit() {
this.isInstalledSoftwareAvailable = this.electronService.isElectronApp;
}
ngOnInit() {}
}

View File

@ -0,0 +1,12 @@
export type SoftwareType = "web";
export class Software {
name: string;
locations: string[];
type: SoftwareType;
resource: string;
binary: string;
sudo: boolean;
installation_arguments: string[];
installed: boolean;
}

View File

@ -0,0 +1,97 @@
import { TestBed } from '@angular/core/testing';
import { ExternalSoftwareDefinitionService } from './external-software-definition.service';
import { PlatformService } from './platform.service';
import Spy = jasmine.Spy;
class PlatformServiceMock {
platform: string;
isWindows() {
return this.platform == 'windows';
}
isLinux() {
return this.platform == 'linux';
}
isDarwin() {
return this.platform == 'darwin';
}
}
describe('ExternalSoftwareDefinitionService', () => {
let platformServiceMock: PlatformServiceMock;
let service: ExternalSoftwareDefinitionService;
beforeEach(() => {
platformServiceMock = new PlatformServiceMock();
});
beforeEach(() => TestBed.configureTestingModule({
providers: [
ExternalSoftwareDefinitionService,
{ provide: PlatformService, useValue: platformServiceMock}
]
}));
beforeEach(() => {
service = TestBed.get(ExternalSoftwareDefinitionService);
})
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should return list for windows', () => {
let software = service.getForWindows();
expect(software.length).toEqual(1);
});
it('should return list for linux', () => {
let software = service.getForLinux();
expect(software.length).toEqual(0);
});
it('should return list for darwin', () => {
let software = service.getForDarwin();
expect(software.length).toEqual(0);
});
describe('ExternalSoftwareDefinitionService.get', () => {
let windowsSpy: Spy;
let darwinSpy: Spy;
let linuxSpy: Spy;
beforeEach(() => {
windowsSpy = spyOn(service, 'getForWindows').and.callThrough();
darwinSpy = spyOn(service, 'getForDarwin').and.callThrough();
linuxSpy = spyOn(service, 'getForLinux').and.callThrough();
});
it('should return list when on windows', () => {
platformServiceMock.platform = 'windows';
expect(service.get()).toBeDefined();
expect(windowsSpy).toHaveBeenCalled();
expect(darwinSpy).not.toHaveBeenCalled();
expect(linuxSpy).not.toHaveBeenCalled();
});
it('should return list when on linux', () => {
platformServiceMock.platform = 'linux';
expect(service.get()).toBeDefined();
expect(windowsSpy).not.toHaveBeenCalled();
expect(darwinSpy).not.toHaveBeenCalled();
expect(linuxSpy).toHaveBeenCalled();
});
it('should return list when on darwin', () => {
platformServiceMock.platform = 'darwin';
expect(service.get()).toBeDefined();
expect(windowsSpy).not.toHaveBeenCalled();
expect(darwinSpy).toHaveBeenCalled();
expect(linuxSpy).not.toHaveBeenCalled();
});
})
});

View File

@ -0,0 +1,65 @@
import { Injectable } from '@angular/core';
import { PlatformService } from './platform.service';
import { environment } from '../../environments/environment';
@Injectable()
export class ExternalSoftwareDefinitionService {
constructor(
private platformService: PlatformService
) { }
get() {
if(this.platformService.isWindows()) {
return this.getForWindows();
}
if(this.platformService.isDarwin()) {
return this.getForDarwin();
}
return this.getForLinux();
}
getForWindows() {
let software = [{
name: 'Wireshark',
locations: [
'C:\\Program Files\\Wireshark\\Wireshark.exe'
],
type: 'web',
resource: 'https://1.na.dl.wireshark.org/win64/all-versions/Wireshark-win64-2.6.3.exe',
binary: 'Wireshark.exe',
sudo: true,
installation_arguments: [],
installed: false
}
];
const solarPutty = {
name: 'SolarPuTTY',
locations: [
'SolarPuTTY.exe'
],
type: 'web',
resource: '',
binary: 'SolarPuTTY.exe',
sudo: false,
installation_arguments: ['--only-ask'],
installed: false
};
if(environment.solarputty_download_url) {
solarPutty.resource = environment.solarputty_download_url;
software.push(solarPutty);
}
return software;
}
getForLinux() {
return [];
}
getForDarwin() {
return [];
}
}

View File

@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';
import { InstalledSoftwareService } from './installed-software.service';
describe('InstalledSoftwareService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
// it('should be created', () => {
// const service: InstalledSoftwareService = TestBed.get(InstalledSoftwareService);
// expect(service).toBeTruthy();
// });
});

View File

@ -0,0 +1,24 @@
import { Injectable } from '@angular/core';
import { ElectronService } from 'ngx-electron';
import { ExternalSoftwareDefinitionService } from './external-software-definition.service';
@Injectable()
export class InstalledSoftwareService {
constructor(
private electronService: ElectronService,
private externalSoftwareDefinition: ExternalSoftwareDefinitionService
) { }
list() {
const softwareDefinition = this.externalSoftwareDefinition.get();
const installedSoftware = this.electronService.remote.require('./installed-software.js')
.getInstalledSoftware(softwareDefinition);
return softwareDefinition.map((software) => {
software.installed = installedSoftware[software.name].length > 0;
return software;
});
}
}

View File

@ -0,0 +1,57 @@
import { TestBed } from '@angular/core/testing';
import { PlatformService } from './platform.service';
import { ElectronService } from 'ngx-electron';
class ElectronServiceMock {
process = {
platform: 'unknown'
};
}
describe('PlatformService', () => {
let electronServiceMock: ElectronServiceMock;
let service: PlatformService;
beforeEach(() => {
electronServiceMock = new ElectronServiceMock();
});
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
PlatformService,
{provide: ElectronService, useValue: electronServiceMock}
]
});
});
beforeEach(() => {
service = TestBed.get(PlatformService);
})
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should be on windows platform', () => {
electronServiceMock.process.platform = 'win32';
expect(service.isWindows()).toBeTruthy();
expect(service.isDarwin()).toBeFalsy();
expect(service.isLinux()).toBeFalsy();
});
it('should be on linux platform', () => {
electronServiceMock.process.platform = 'linux';
expect(service.isWindows()).toBeFalsy();
expect(service.isDarwin()).toBeFalsy();
expect(service.isLinux()).toBeTruthy();
});
it('should be on darwin platform', () => {
electronServiceMock.process.platform = 'darwin';
expect(service.isWindows()).toBeFalsy();
expect(service.isDarwin()).toBeTruthy();
expect(service.isLinux()).toBeFalsy();
});
});

View File

@ -0,0 +1,22 @@
import { Injectable } from '@angular/core';
import { ElectronService } from 'ngx-electron';
@Injectable()
export class PlatformService {
constructor(
private electronService: ElectronService
) { }
isWindows() {
return this.electronService.process.platform === 'win32';
}
isLinux() {
return this.electronService.process.platform === 'linux';
}
isDarwin() {
return this.electronService.process.platform === 'darwin';
}
}

View File

@ -1,4 +1,5 @@
export const environment = {
production: true,
electron: true
electron: true,
solarputty_download_url: ""
};

View File

@ -1,4 +1,5 @@
export const environment = {
production: false,
electron: true
electron: true,
solarputty_download_url: ""
};

View File

@ -1,5 +1,6 @@
export const environment = {
production: true,
electron: false,
githubio: true
githubio: true,
solarputty_download_url: ""
};

View File

@ -1,5 +1,7 @@
export const environment = {
/* Below configuration is replaced during build time, don't remove. */
//ENV
production: true,
electron: false,
githubio: false
githubio: false,
};

View File

@ -5,7 +5,8 @@
export const environment = {
production: false,
electron: false,
githubio: false
githubio: false,
solarputty_download_url: ''
};
/*

View File

@ -2035,6 +2035,11 @@ colors@1.1.2:
resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
integrity sha1-FopHAXVran9RoSzgyXv6KMCE7WM=
colors@1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.4.tgz#e0cb41d3e4b20806b3bfc27f4559f01b94bc2f7c"
integrity sha512-6Y+iBnWmXL+AWtlOp2Vr6R2w5MUlNJRwR0ShVFaAb1CqWzhPOpQg4L0jxD+xpw/Nc8QJwaq3KM79QUCriY8CWQ==
colors@^1.1.0:
version "1.3.3"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d"
@ -2061,6 +2066,11 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
command-exists@^1.2.8:
version "1.2.8"
resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.8.tgz#715acefdd1223b9c9b37110a149c6392c2852291"
integrity sha512-PM54PkseWbiiD/mMsbvW351/u+dafwTJ0ye2qB60G1aGQP9j3xK2gmMDc+R34L3nDtx4qMCitXT75mkbkGJDLw==
commander@2, commander@^2.12.1:
version "2.19.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
@ -5941,6 +5951,11 @@ node-fetch-npm@^2.0.2:
json-parse-better-errors "^1.0.0"
safe-buffer "^5.1.1"
node-fetch@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.3.0.tgz#1a1d940bbfb916a1d3e0219f037e89e71f8c5fa5"
integrity sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA==
node-forge@0.7.5:
version "0.7.5"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.5.tgz#6c152c345ce11c52f465c2abd957e8639cd674df"
@ -7300,6 +7315,15 @@ repeating@^2.0.0:
dependencies:
is-finite "^1.0.0"
replace@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/replace/-/replace-1.0.1.tgz#a7838aaf5977d443e903aea4ba52b2c7ac432190"
integrity sha512-Qh0XcLMb3LYa6fs7V30zQHACbJTQJUERl22lVjaq0dJp6B5q1t/vARXDauS1ywpIs3ZkT3APj4EA6aOoHoaHDA==
dependencies:
colors "1.2.4"
minimatch "3.0.4"
yargs "12.0.5"
request@^2.45.0, request@^2.83.0, request@^2.87.0, request@^2.88.0:
version "2.88.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
@ -9240,6 +9264,24 @@ yargs@12.0.2:
y18n "^3.2.1 || ^4.0.0"
yargs-parser "^10.1.0"
yargs@12.0.5, yargs@^12.0.5:
version "12.0.5"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13"
integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==
dependencies:
cliui "^4.0.0"
decamelize "^1.2.0"
find-up "^3.0.0"
get-caller-file "^1.0.1"
os-locale "^3.0.0"
require-directory "^2.1.1"
require-main-filename "^1.0.1"
set-blocking "^2.0.0"
string-width "^2.0.0"
which-module "^2.0.0"
y18n "^3.2.1 || ^4.0.0"
yargs-parser "^11.1.1"
yargs@9.0.1:
version "9.0.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-9.0.1.tgz#52acc23feecac34042078ee78c0c007f5085db4c"
@ -9259,24 +9301,6 @@ yargs@9.0.1:
y18n "^3.2.1"
yargs-parser "^7.0.0"
yargs@^12.0.5:
version "12.0.5"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13"
integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==
dependencies:
cliui "^4.0.0"
decamelize "^1.2.0"
find-up "^3.0.0"
get-caller-file "^1.0.1"
os-locale "^3.0.0"
require-directory "^2.1.1"
require-main-filename "^1.0.1"
set-blocking "^2.0.0"
string-width "^2.0.0"
which-module "^2.0.0"
y18n "^3.2.1 || ^4.0.0"
yargs-parser "^11.1.1"
yargs@^7.0.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8"