project files added

This commit is contained in:
mhalfmann
2021-06-15 16:00:08 +02:00
parent e156e2f053
commit db46afa351
13928 changed files with 1569902 additions and 0 deletions
@@ -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;
@@ -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
View File
@@ -0,0 +1,4 @@
var ProcessManager = require('./ProcessManager.js');
// singleton
module.exports = new ProcessManager();
@@ -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>
)
}
}
@@ -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>
)
}
}
}
@@ -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>
)
}
}
@@ -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>&nbsp;</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}
&nbsp;
{this.getSortCharacter()}
</th>
)
}
}
+35
View File
@@ -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
View File
@@ -0,0 +1,6 @@
import React from 'react';
import { render } from 'react-dom';
import ProcessManager from './ProcessManager';
render(<ProcessManager/>, document.getElementById('app'));