project files added
This commit is contained in:
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"presets" : ["stage-0", "react"]
|
||||
}
|
||||
Generated
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 253 KiB |
+79
@@ -0,0 +1,79 @@
|
||||
# Process Manager UI for Electron Apps
|
||||
|
||||
This package provides a process manager UI for Electron applications.
|
||||
|
||||
It opens a window displaying a table of every processes run by the Electron application with information (type, URL for `webContents`, memory..).
|
||||
|
||||
[](https://badge.fury.io/js/electron-process-manager)
|
||||
|
||||

|
||||
|
||||
:warning: For `@electron>=3.0.0`
|
||||
|
||||
It can be useful to debug performance of an app with several `webview`.
|
||||
|
||||
It's inspired from Chrome's task manager.
|
||||
|
||||
## Features
|
||||
|
||||
- [ ] Memory reporting
|
||||
- [ ] Link memory data to web-contents (for electron >=1.7.1)
|
||||
- [x] Kill a process from the UI
|
||||
- [x] Open developer tools for a given process
|
||||
- [x] CPU metrics
|
||||
- [x] Sort by columns
|
||||
|
||||
⚠️ Unfortunately, memory info are no longer available in Electron>=4 (see [electron/electron#16179](https://github.com/electron/electron/issues/16179))
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
$ npm install electron-process-manager
|
||||
```
|
||||
|
||||
## Usage
|
||||
```js
|
||||
const { openProcessManager } = require('electron-process-manager');
|
||||
|
||||
openProcessManager();
|
||||
```
|
||||
|
||||
## Options
|
||||
`openProcessManager` function can take options in paramters
|
||||
|
||||
#### options.defaultSorting
|
||||
**defaultSorting.how**: `'ascending' | 'descending'`
|
||||
|
||||
**defaultSorting.path**:
|
||||
|
||||
| Field name | path |
|
||||
|--------------------|----------------------------|
|
||||
| Pid | 'pid' |
|
||||
| WebContents Domain | 'webContents.0.URLDomain' |
|
||||
| Process Type | 'webContents.0.type' |
|
||||
| Private Memory | 'memory.privateBytes' |
|
||||
| Shared Memory | 'memory.sharedBytes' |
|
||||
| Working Set Size | 'memory.workingSetSize' |
|
||||
| % CPU | 'cpu.percentCPUUsage' |
|
||||
| Idle Wake Ups /s | 'cpu.idleWakeupsPerSecond' |
|
||||
| WebContents Id | 'webContents.0.id' |
|
||||
| WebContents Type | 'webContents.0.type' |
|
||||
| WebContents URL | 'webContents.0.URL' |
|
||||
|
||||
example:
|
||||
```js
|
||||
const { openProcessManager } = require('electron-process-manager');
|
||||
|
||||
openProcessManager({ how: 'descending', path: 'cpu.percentCPUUsage' });
|
||||
```
|
||||
|
||||
## Future
|
||||
|
||||
- Add physical memory (noted as "Memory" in Chrome's task manager)
|
||||
- Add networks metrics
|
||||
|
||||
Pull requests welcome :)
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
+23343
File diff suppressed because it is too large
Load Diff
+79
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"_args": [
|
||||
[
|
||||
"electron-process-manager@0.7.1",
|
||||
"C:\\Daten\\Git\\Tumortisch"
|
||||
]
|
||||
],
|
||||
"_from": "electron-process-manager@0.7.1",
|
||||
"_id": "electron-process-manager@0.7.1",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-GE7Sok7IfObhVPqx7n+wVBpdFxVvR/ySa56sLsTT4rnYKBFDWtdrVMiXgKjhHjxsxO59Y+piXT6QonamrQFWeA==",
|
||||
"_location": "/electron-process-manager",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "version",
|
||||
"registry": true,
|
||||
"raw": "electron-process-manager@0.7.1",
|
||||
"name": "electron-process-manager",
|
||||
"escapedName": "electron-process-manager",
|
||||
"rawSpec": "0.7.1",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "0.7.1"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/electron-process-manager/-/electron-process-manager-0.7.1.tgz",
|
||||
"_spec": "0.7.1",
|
||||
"_where": "C:\\Daten\\Git\\Tumortisch",
|
||||
"author": "",
|
||||
"bugs": {
|
||||
"url": "https://github.com/getstation/electron-process-manager/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"electron-process-reporter": "^1.4.0"
|
||||
},
|
||||
"description": "Process manager UI for Electron applications",
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.24.1",
|
||||
"babel-loader": "^7.0.0",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"babel-preset-stage-0": "^6.24.1",
|
||||
"bluebird": "^3.5.4",
|
||||
"bluebird-extra": "^2.0.0",
|
||||
"electron": "^5.0.0",
|
||||
"electron-default-menu": "1.0.1",
|
||||
"filesize": "^3.5.11",
|
||||
"format-number": "^3.0.0",
|
||||
"object-path": "^0.11.4",
|
||||
"prop-types": "^15.5.10",
|
||||
"react": "^15.5.4",
|
||||
"react-dom": "^15.5.4",
|
||||
"spectron": "^5.0.0",
|
||||
"webpack": "^2.5.1"
|
||||
},
|
||||
"homepage": "https://github.com/getstation/electron-process-manager#readme",
|
||||
"license": "MIT",
|
||||
"main": "src/index.js",
|
||||
"name": "electron-process-manager",
|
||||
"peerDependencies": {
|
||||
"electron": ">=1.7.1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/getstation/electron-process-manager.git"
|
||||
},
|
||||
"resolutions": {
|
||||
"spectron/**/electron-chromedriver": "^4.0.0-beta.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack",
|
||||
"dev": "webpack -d --watch",
|
||||
"example": "npm run build && electron example",
|
||||
"prepare": "npm run build",
|
||||
"test": "npm run build && node tests/test.js"
|
||||
},
|
||||
"version": "0.7.1"
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Process Manager</title>
|
||||
<link rel="stylesheet" href="vendor/photon.css">
|
||||
<style>
|
||||
.process-table {
|
||||
/*margin: 10px;*/
|
||||
}
|
||||
.process-table-container {
|
||||
overflow-x: scroll;
|
||||
flex: 2;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" class="window"></div>
|
||||
<script src="dist/ui-bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
const { EventEmitter } = require('events');
|
||||
const process = require('process');
|
||||
const { webContents } = require('electron');
|
||||
|
||||
var ProcessManagerWindow = require('./ProcessManagerWindow.js');
|
||||
|
||||
const defaultOptions = { defaultSorting: { path: null, how: null } };
|
||||
|
||||
class ProcessManager extends EventEmitter {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// legacy
|
||||
this.openProcessManager = this.open.bind(this);
|
||||
}
|
||||
|
||||
open (options = defaultOptions) {
|
||||
if (this.window) {
|
||||
this.window.focus();
|
||||
}
|
||||
|
||||
this.window = new ProcessManagerWindow();
|
||||
this.window.defaultSorting = options.defaultSorting || {};
|
||||
this.window.showWhenReady();
|
||||
this.window.on('kill-process', pid => this.killProcess(pid))
|
||||
this.window.on('open-dev-tools', webContentsId => this.openDevTools(webContentsId))
|
||||
this.window.on('closed', () => this.window = null)
|
||||
this.emit('open-window', this.window);
|
||||
|
||||
return this.window;
|
||||
}
|
||||
|
||||
killProcess(pid) {
|
||||
this.emit('will-kill-process', pid, this.window);
|
||||
process.kill(pid);
|
||||
this.emit('killed-process', pid, this.window);
|
||||
}
|
||||
|
||||
openDevTools(webContentsId) {
|
||||
this.emit('will-open-dev-tools', webContentsId, this.window);
|
||||
|
||||
const wc = webContents.fromId(webContentsId);
|
||||
wc.openDevTools({ mode: 'detach' });
|
||||
|
||||
this.emit('did-open-dev-tools', webContentsId, this.window);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = ProcessManager;
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
const { app, BrowserWindow, shell, ipcMain, webContents } = require('electron');
|
||||
const path = require('path');
|
||||
const process = require('process');
|
||||
|
||||
const { onExtendedProcessMetrics } = require('electron-process-reporter');
|
||||
|
||||
class ProcessManagerWindow extends BrowserWindow {
|
||||
|
||||
constructor(options) {
|
||||
const winOptions = Object.assign({
|
||||
width: 800,
|
||||
height: 300,
|
||||
useContentSize: true,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
webviewTag: true,
|
||||
}
|
||||
}, options || {});
|
||||
|
||||
super(winOptions);
|
||||
this.options = options;
|
||||
|
||||
this.attachProcessReporter();
|
||||
|
||||
const indexHtml = 'file://' + path.join(__dirname, '..', 'process-manager.html');
|
||||
this.loadURL(indexHtml);
|
||||
}
|
||||
|
||||
showWhenReady() {
|
||||
this.once('ready-to-show', () => {
|
||||
this.show();
|
||||
});
|
||||
}
|
||||
|
||||
sendStatsReport(reportData) {
|
||||
if (!this.webContents) return;
|
||||
this.webContents.send('process-manager:data', reportData);
|
||||
}
|
||||
|
||||
openDevTools() {
|
||||
this.webContents.openDevTools();
|
||||
}
|
||||
|
||||
attachProcessReporter() {
|
||||
this.subscription = onExtendedProcessMetrics(app)
|
||||
.subscribe(report => this.sendStatsReport(report))
|
||||
ipcMain.on('process-manager:kill-process', (e, pid) => {
|
||||
// ignore if not for us
|
||||
if (!this || this.isDestroyed()) return;
|
||||
if (e.sender !== this.webContents) return;
|
||||
|
||||
this.emit('kill-process', pid);
|
||||
});
|
||||
ipcMain.on('process-manager:open-dev-tools', (e, webContentsId) => {
|
||||
// ignore if not for us
|
||||
if (!this || this.isDestroyed()) return;
|
||||
if (e.sender !== this.webContents) return;
|
||||
|
||||
|
||||
this.emit('open-dev-tools', webContentsId);
|
||||
|
||||
});
|
||||
this.on('closed', () => {
|
||||
if (this.subscription) this.subscription.unsubscribe()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ProcessManagerWindow;
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
var ProcessManager = require('./ProcessManager.js');
|
||||
|
||||
// singleton
|
||||
module.exports = new ProcessManager();
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
import React from 'react';
|
||||
import { ipcRenderer, remote } from 'electron';
|
||||
import objectPath from 'object-path';
|
||||
|
||||
import ProcessTable from './ProcessTable';
|
||||
import ToolBar from './ToolBar';
|
||||
|
||||
export default class ProcessManager extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
processData: null,
|
||||
selectedPid: null,
|
||||
sorting: {
|
||||
path: null,
|
||||
how: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.setState({ sorting: remote.getCurrentWindow().defaultSorting });
|
||||
ipcRenderer.on('process-manager:data', (_, data) => {
|
||||
this.setState({ processData: data });
|
||||
})
|
||||
}
|
||||
|
||||
canKill() {
|
||||
if (!this.state.selectedPid) return false;
|
||||
const pids = this.state.processData.map(p => p.pid);
|
||||
|
||||
// verify that select pid is in list of processes
|
||||
return pids.indexOf(this.state.selectedPid) !== -1;
|
||||
}
|
||||
|
||||
canOpenDevTool() {
|
||||
return this.canKill() && this.getWebContentsIdForSelectedProcess() !== null;
|
||||
}
|
||||
|
||||
getWebContentsIdForSelectedProcess() {
|
||||
const { processData, selectedPid } = this.state;
|
||||
if (!selectedPid) return null;
|
||||
|
||||
const process = processData.find(p => p.pid === selectedPid);
|
||||
if (!process || !process.webContents || process.webContents.length === 0) return null;
|
||||
|
||||
return process.webContents[0].id;
|
||||
}
|
||||
|
||||
handleKillProcess() {
|
||||
const pid = this.state.selectedPid;
|
||||
if (!pid) return;
|
||||
ipcRenderer.send('process-manager:kill-process', pid);
|
||||
}
|
||||
|
||||
handleOpenDevTool() {
|
||||
const webContentsId = this.getWebContentsIdForSelectedProcess();
|
||||
ipcRenderer.send('process-manager:open-dev-tools', webContentsId);
|
||||
}
|
||||
|
||||
getProcessData() {
|
||||
const { processData, sorting } = this.state;
|
||||
|
||||
if (!sorting.path || !sorting.how) return processData;
|
||||
|
||||
return processData.sort((p1, p2) => {
|
||||
const p1Metric = objectPath.get(p1, sorting.path);
|
||||
const p2Metric = objectPath.get(p2, sorting.path);
|
||||
|
||||
if (p1Metric === p2Metric) return 0;
|
||||
const comp = p1Metric < p2Metric ? -1 : 1;
|
||||
|
||||
return sorting.how == 'ascending' ? comp : -comp;
|
||||
});
|
||||
}
|
||||
|
||||
render () {
|
||||
const { processData } = this.state;
|
||||
if (!processData) return (<span>No data</span>);
|
||||
|
||||
return (
|
||||
<div className="window">
|
||||
<header className="toolbar toolbar-header">
|
||||
<ToolBar
|
||||
disableKill={!this.canKill()}
|
||||
onKillClick={this.handleKillProcess.bind(this)}
|
||||
disabelOpenDevTool={!this.canOpenDevTool()}
|
||||
onOpenDevToolClick={this.handleOpenDevTool.bind(this)}
|
||||
|
||||
/>
|
||||
</header>
|
||||
<div className="process-table-container">
|
||||
<ProcessTable
|
||||
processData={this.getProcessData()}
|
||||
selectedPid={this.state.selectedPid}
|
||||
sorting={this.state.sorting}
|
||||
onSortingChange={sorting => this.setState({ sorting })}
|
||||
onSelectedPidChange={pid => this.setState({ selectedPid: pid })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import filesize from 'filesize';
|
||||
import format from 'format-number';
|
||||
|
||||
const KB = 1024;
|
||||
const formatPercentage = format({
|
||||
round: 1,
|
||||
padRight: 1
|
||||
});
|
||||
|
||||
export default class ProcessRow extends React.Component {
|
||||
static propTypes = {
|
||||
pid: PropTypes.number,
|
||||
type: PropTypes.string,
|
||||
memory: PropTypes.shape({
|
||||
peakWorkingSetSize: PropTypes.number,
|
||||
privateBytes: PropTypes.number,
|
||||
sharedBytes: PropTypes.number,
|
||||
workingSetSize: PropTypes.number
|
||||
}),
|
||||
cpu: PropTypes.shape({
|
||||
percentCPUUsage: PropTypes.number,
|
||||
idleWakeupsPerSecond: PropTypes.number
|
||||
}),
|
||||
webContents: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.number,
|
||||
type: PropTypes.string,
|
||||
URL: PropTypes.string,
|
||||
URLDomain: PropTypes.string
|
||||
})),
|
||||
selected: PropTypes.bool,
|
||||
onSelect: PropTypes.func
|
||||
}
|
||||
|
||||
render() {
|
||||
const { webContents, memory } = this.props;
|
||||
if (!webContents || webContents.length === 0) {
|
||||
return (
|
||||
<tr
|
||||
className={this.props.selected ? 'selected': ''}
|
||||
onClick={this.props.onSelect}
|
||||
>
|
||||
<td>{this.props.pid}</td>
|
||||
<td></td>
|
||||
<td>{this.props.type}</td>
|
||||
<td>{memory ? filesize(memory.privateBytes*KB) : 'N/A'}</td>
|
||||
<td>{memory ? filesize(memory.sharedBytes*KB) : 'N/A'}</td>
|
||||
<td>{memory ? filesize(memory.workingSetSize*KB) : 'N/A'}</td>
|
||||
<td>{formatPercentage(this.props.cpu.percentCPUUsage)}</td>
|
||||
<td>{this.props.cpu.idleWakeupsPerSecond}</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
)
|
||||
} else {
|
||||
// FIX ME: we consider we have only have 1 webContents per process
|
||||
const wc = webContents[0];
|
||||
|
||||
return (
|
||||
<tr
|
||||
className={this.props.selected ? 'selected': ''}
|
||||
onClick={this.props.onSelect}
|
||||
>
|
||||
<td>{this.props.pid}</td>
|
||||
<td>{wc.URLDomain}</td>
|
||||
<td>{this.props.type}</td>
|
||||
<td>{memory ? filesize(memory.privateBytes*KB) : 'N/A'}</td>
|
||||
<td>{memory ? filesize(memory.sharedBytes*KB) : 'N/A'}</td>
|
||||
<td>{memory ? filesize(memory.workingSetSize*KB) : 'N/A'}</td>
|
||||
<td>{formatPercentage(this.props.cpu.percentCPUUsage)}</td>
|
||||
<td>{this.props.cpu.idleWakeupsPerSecond}</td>
|
||||
<td>{wc.id}</td>
|
||||
<td>{wc.type}</td>
|
||||
<td>{wc.URL}</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import ProcessRow from './ProcessRow';
|
||||
import ProcessTableHeader from './ProcessTableHeader';
|
||||
|
||||
export default class ProcessTable extends React.Component {
|
||||
static propTypes = {
|
||||
processData: PropTypes.arrayOf(PropTypes.object),
|
||||
selectedPid: PropTypes.number,
|
||||
sorting: PropTypes.PropTypes.shape({
|
||||
path: PropTypes.string,
|
||||
how: PropTypes.string
|
||||
}),
|
||||
onSortingChange: PropTypes.func,
|
||||
onSelectedPidChange: PropTypes.func
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<table className="process-table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<ProcessTableHeader
|
||||
path='pid'
|
||||
sorting={this.props.sorting}
|
||||
onSortingChange={this.props.onSortingChange}
|
||||
>Pid</ProcessTableHeader>
|
||||
|
||||
<ProcessTableHeader
|
||||
path='webContents.0.URLDomain'
|
||||
sorting={this.props.sorting}
|
||||
onSortingChange={this.props.onSortingChange}
|
||||
>WebContents Domain</ProcessTableHeader>
|
||||
|
||||
<ProcessTableHeader
|
||||
path='webContents.0.type'
|
||||
sorting={this.props.sorting}
|
||||
onSortingChange={this.props.onSortingChange}
|
||||
>Process Type</ProcessTableHeader>
|
||||
|
||||
<ProcessTableHeader
|
||||
path='memory.privateBytes'
|
||||
sorting={this.props.sorting}
|
||||
onSortingChange={this.props.onSortingChange}
|
||||
>Private Memory</ProcessTableHeader>
|
||||
|
||||
<ProcessTableHeader
|
||||
path='memory.sharedBytes'
|
||||
sorting={this.props.sorting}
|
||||
onSortingChange={this.props.onSortingChange}
|
||||
>Shared Memory</ProcessTableHeader>
|
||||
|
||||
<ProcessTableHeader
|
||||
path='memory.workingSetSize'
|
||||
sorting={this.props.sorting}
|
||||
onSortingChange={this.props.onSortingChange}
|
||||
>Working Set Size</ProcessTableHeader>
|
||||
|
||||
<ProcessTableHeader
|
||||
path='cpu.percentCPUUsage'
|
||||
sorting={this.props.sorting}
|
||||
onSortingChange={this.props.onSortingChange}
|
||||
>% CPU</ProcessTableHeader>
|
||||
|
||||
<ProcessTableHeader
|
||||
path='cpu.idleWakeupsPerSecond'
|
||||
sorting={this.props.sorting}
|
||||
onSortingChange={this.props.onSortingChange}
|
||||
>Idle Wake Ups /s</ProcessTableHeader>
|
||||
|
||||
<ProcessTableHeader
|
||||
path='webContents.0.id'
|
||||
sorting={this.props.sorting}
|
||||
onSortingChange={this.props.onSortingChange}
|
||||
>WebContents Id</ProcessTableHeader>
|
||||
|
||||
<ProcessTableHeader
|
||||
path='webContents.0.type'
|
||||
sorting={this.props.sorting}
|
||||
onSortingChange={this.props.onSortingChange}
|
||||
>WebContents Type</ProcessTableHeader>
|
||||
|
||||
<ProcessTableHeader
|
||||
path='webContents.0.URL'
|
||||
sorting={this.props.sorting}
|
||||
onSortingChange={this.props.onSortingChange}
|
||||
>WebContents URL</ProcessTableHeader>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
this.props.processData.map(p =>
|
||||
<ProcessRow
|
||||
key={p.pid}
|
||||
{...p}
|
||||
onSelect={() => this.props.onSelectedPidChange(p.pid)}
|
||||
selected={this.props.selectedPid === p.pid}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
|
||||
export default class ProcessTableHeader extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
path: PropTypes.string.isRequired,
|
||||
sorting: PropTypes.PropTypes.shape({
|
||||
path: PropTypes.string,
|
||||
how: PropTypes.string
|
||||
}),
|
||||
onSortingChange: PropTypes.func
|
||||
}
|
||||
|
||||
getSortCharacter() {
|
||||
if (!this.sortHow) return (
|
||||
<span> </span>
|
||||
);
|
||||
return this.sortHow == 'ascending' ? '👆' : '👇'
|
||||
}
|
||||
|
||||
get sortHow() {
|
||||
if (!this.props.sorting) return null;
|
||||
|
||||
if (this.props.sorting.path == this.props.path){
|
||||
return this.props.sorting.how;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
let nextSortHow = null;
|
||||
if(this.sortHow === null) {
|
||||
nextSortHow = 'ascending';
|
||||
} else if (this.sortHow === 'ascending') {
|
||||
nextSortHow = 'descending';
|
||||
} else {
|
||||
nextSortHow = null;
|
||||
}
|
||||
this.props.onSortingChange({
|
||||
path: this.props.path,
|
||||
how: nextSortHow
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<th onClick={this.handleClick}>
|
||||
{this.props.children}
|
||||
|
||||
{this.getSortCharacter()}
|
||||
</th>
|
||||
)
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export default class ToolBar extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
onKillClick: PropTypes.func,
|
||||
disableKill: PropTypes.bool,
|
||||
onOpenDevToolClick: PropTypes.func,
|
||||
disabelOpenDevTool: PropTypes.bool
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="toolbar-actions">
|
||||
<div className="btn-group">
|
||||
<button
|
||||
className="btn btn-default"
|
||||
disabled={this.props.disableKill}
|
||||
onClick={this.props.onKillClick}
|
||||
>
|
||||
End process
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-default"
|
||||
disabled={this.props.disabelOpenDevTool}
|
||||
onClick={this.props.onOpenDevToolClick}
|
||||
>
|
||||
Open Dev Tool
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
|
||||
import ProcessManager from './ProcessManager';
|
||||
|
||||
render(<ProcessManager/>, document.getElementById('app'));
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
const Application = require('spectron').Application;
|
||||
const { join } = require('path');
|
||||
const assert = require('assert')
|
||||
|
||||
const { setUpApp } = require('./utils');
|
||||
|
||||
const app = new Application({
|
||||
env: { TEST_PROCESS_MANAGER: 1 },
|
||||
path: require(join(__dirname, '../node_modules/electron')),
|
||||
args: [join(__dirname, '../example/main.js')]
|
||||
});
|
||||
|
||||
app.start()
|
||||
.then(() => {
|
||||
setUpApp(app);
|
||||
return app.client.waitUntilWindowLoaded();
|
||||
})
|
||||
.then(() => app.electron.ipcRenderer.send('open-process-manager'))
|
||||
.then(() => app.client.waitUntilWindowLoaded())
|
||||
.then(() => assert(app.client.getWindowCount(), 2))
|
||||
.then(() => app.client.changeFocusToMatchingURL(/process-manager\.html/))
|
||||
.then(() => app.client.waitForVisible('#app .process-table'))
|
||||
.then(() => app.stop())
|
||||
.catch(function (error) {
|
||||
console.error('Test failed', error);
|
||||
if (app && app.isRunning()) {
|
||||
app.stop().then(() => process.exit(1))
|
||||
} else process.exit(1);
|
||||
})
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
const Promise = require('bluebird');
|
||||
require('bluebird-extra').usePromise(Promise);
|
||||
|
||||
const setUpApp = function(app) {
|
||||
app.client.addCommand('changeFocusToMatchingURL', URLRegexp => {
|
||||
return app.client.windowHandles()
|
||||
.then(handles => Promise.eachAny(handles.value, handle => {
|
||||
return app.client.window(handle)
|
||||
.then(() => app.client.getUrl())
|
||||
.then(url => !!url.match(URLRegexp) || undefined)
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
module.exports.setUpApp = setUpApp;
|
||||
+2341
File diff suppressed because it is too large
Load Diff
+24
@@ -0,0 +1,24 @@
|
||||
const webpack = require('webpack');
|
||||
const path = require('path');
|
||||
|
||||
const BUILD_DIR = path.resolve(__dirname, 'dist');
|
||||
|
||||
const config = {
|
||||
entry: path.resolve(__dirname, 'src/ui/index.js'),
|
||||
output: {
|
||||
path: BUILD_DIR,
|
||||
filename: 'ui-bundle.js'
|
||||
},
|
||||
module : {
|
||||
loaders : [
|
||||
{
|
||||
test: /\.(jsx|js)?$/,
|
||||
loader : 'babel-loader',
|
||||
include: path.resolve(__dirname, 'src/ui')
|
||||
}
|
||||
]
|
||||
},
|
||||
target: 'electron-renderer'
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
+3997
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user