mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-04-09 03:24:13 +00:00
Merge pull request #276 from GNS3/dependencies-list
Installation of external software on Windows, Ref. #251, #258
This commit is contained in:
commit
b4d9b28392
3
.gitignore
vendored
3
.gitignore
vendored
@ -52,3 +52,6 @@ Thumbs.db
|
||||
|
||||
# Licenses file
|
||||
licenses.csv
|
||||
|
||||
# Temps
|
||||
.temp-var-file.ts
|
||||
|
@ -13,6 +13,7 @@ files:
|
||||
- main.js
|
||||
- renderer.js
|
||||
- sentry.js
|
||||
- installed-software.js
|
||||
- package.json
|
||||
|
||||
mac:
|
||||
|
88
installed-software.js
Normal file
88
installed-software.js
Normal 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();
|
||||
});
|
5
main.js
5
main.js
@ -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.
|
||||
|
||||
|
||||
|
@ -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
21
set-variables-in-env.js
Normal 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);
|
||||
}
|
@ -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 }
|
||||
|
@ -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,
|
||||
|
@ -0,0 +1,3 @@
|
||||
<button mat-button color="primary" (click)="install()" [disabled]="disabled">
|
||||
<ng-container *ngIf="readyToInstall">{{ buttonText }}</ng-container>
|
||||
</button>
|
@ -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();
|
||||
// });
|
||||
});
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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();
|
||||
// });
|
||||
});
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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>
|
||||
|
@ -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 © 2018</footer>
|
||||
<footer class="footer mat-app-background">
|
||||
GNS3 Web UI © 2019
|
||||
</footer>
|
||||
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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() {}
|
||||
}
|
||||
|
12
src/app/models/software.ts
Normal file
12
src/app/models/software.ts
Normal 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;
|
||||
}
|
@ -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();
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
});
|
65
src/app/services/external-software-definition.service.ts
Normal file
65
src/app/services/external-software-definition.service.ts
Normal 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 [];
|
||||
}
|
||||
}
|
12
src/app/services/installed-software.service.spec.ts
Normal file
12
src/app/services/installed-software.service.spec.ts
Normal 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();
|
||||
// });
|
||||
});
|
24
src/app/services/installed-software.service.ts
Normal file
24
src/app/services/installed-software.service.ts
Normal 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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
57
src/app/services/platform.service.spec.ts
Normal file
57
src/app/services/platform.service.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
22
src/app/services/platform.service.ts
Normal file
22
src/app/services/platform.service.ts
Normal 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';
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
export const environment = {
|
||||
production: true,
|
||||
electron: true
|
||||
electron: true,
|
||||
solarputty_download_url: ""
|
||||
};
|
||||
|
@ -1,4 +1,5 @@
|
||||
export const environment = {
|
||||
production: false,
|
||||
electron: true
|
||||
electron: true,
|
||||
solarputty_download_url: ""
|
||||
};
|
||||
|
@ -1,5 +1,6 @@
|
||||
export const environment = {
|
||||
production: true,
|
||||
electron: false,
|
||||
githubio: true
|
||||
githubio: true,
|
||||
solarputty_download_url: ""
|
||||
};
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -5,7 +5,8 @@
|
||||
export const environment = {
|
||||
production: false,
|
||||
electron: false,
|
||||
githubio: false
|
||||
githubio: false,
|
||||
solarputty_download_url: ''
|
||||
};
|
||||
|
||||
/*
|
||||
|
60
yarn.lock
60
yarn.lock
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user