Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,17 @@ Awesome Redis GUI written in Electron, NodeJS and React

- `redisinsight/ui` - Contains the frontend code
- `redisinsight/api` - Contains the backend code
- `docs` - Contains the documentation
- `scripts` - Build scripts and other build-related files
- `configs` - Webpack configuration files and other build-related files
- `tests` - Contains the e2e

## Plugins documentation

* [Introduction](docs/plugins/introduction.md)
* [Installation and Usage](docs/plugins/installation.md)
* [Plugin Development](docs/plugins/development.md)

## Prerequisites

Make sure you have installed following packages:
Expand Down
182 changes: 182 additions & 0 deletions docs/plugins/development.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# Plugin development

This document describes the guides to develop your own plugin for the RedisInsight Workbench.

## How it works

Plugin visualization in the Workbench is rendered using Iframe to encapsulate plugin scripts and styles, described in
the main plugin script and the stylesheet (if it has been specified in the `package.json`),
iframe includes basic styles as well.

## Plugin structure

Each plugin should have a unique name with all its files [loaded](installation.md) to
a separate folder inside the default `plugins` folder.

> Default plugins are located inside the application.

### Files
`package.json` should be located in the root folder of your plugins, all other files can be included into a subfolder.

* **pluginName/package.json** *(required)* - Manifest of the plugin
* **pluginName/{anyName}.js** *(required)* - Core script of the plugin
* **pluginName/{anyName}.css** *(optional)* - File with styles for the plugin visualizations
* **pluginName/{anyFileOrFolder}** *(optional)* - Specify any other file or folder inside the plugin folder
to use by the core module script. *For example*: pluginName/images/image.png.

## `package.json` structure

This is the required manifest to use the plugin. `package.json` file should include
the following **required** fields:

<table>
<tr>
<td><i>name</i></td>
<td>Plugin name. It is recommended to use the folder name as the plugin name in the package.json.</td>
</tr>
<tr>
<td><i>main</i></td>
<td>Relative path to the core script of the plugin. <i>Example: </i> "./dist/index.js"</td>
</tr>
<tr>
<td><i>visualizations</i></td>
<td>
Array of visualizations (objects) to visualize the results in the Workbench.
<br><br>
Required fields in visualizations:
<ul>
<li><strong><i>id</i></strong> - visualization id</li>
<li><strong><i>name</i></strong> - visualization name to display in the Workbench</li>
<li><strong><i>activationMethod</i></strong> - name of the exported function to call when
this visualization is selected in the Workbench</li>
<li>
<strong><i>matchCommands</i></strong> - array of commands to use the visualization for. Supports regex string.
<i>Example: </i> ["CLIENT LIST", "FT.*"]
</li>
</ul>
</td>
</tr>
</table>

You can specify the path to a css file in the `styles` field. If specified,
this file will be included inside the iframe plugin.

Simple example of the `package.json` file with required and optional fields:

```json
{
"author": {
"name": "Redis Ltd.",
"email": "support@redis.com",
"url": "https://redis.com/redis-enterprise/redis-insight"
},
"description": "Show client list as table",
"styles": "./dist/styles.css",
"main": "./dist/index.js",
"name": "client-list",
"version": "0.0.1",
"scripts": {},
"visualizations": [
{
"id": "clients-list",
"name": "Table",
"activationMethod": "renderClientsList",
"matchCommands": [
"CLIENT LIST"
],
"description": "Example of client list plugin",
"default": true
}
],
"devDependencies": {},
"dependencies": {}
}
```

## Core script of the plugin

This is the required script with defined visualization methods.
The core script contains function and its export (functions - for multiple visualizations),
which is run after the relevant visualization is selected in the Workbench.

The following function receives props of the executed commands:
```typescript
interface Props {
command: string; // executed command
data: string; // result of the executed command
status: 'success' | 'fail'; // response status of the executed command
}

const renderVisualization = (props: Props) => {
// Do your magic
}

export default { renderVisualization }
```

Each plugin iframe has basic styles of RedisInsight application, including fonts and color schemes.

It is recommended to use the React & [Elastic UI library](https://elastic.github.io/eui/#/) for
consistency with plugin visualisations and the entire application.

Find the example of the plugin here.

* [Client List Plugin README](../../redisinsight/ui/src/packages/clients-list-example/README.md)
* [Client List Plugin dir](../../redisinsight/ui/src/packages/clients-list-example/)

### Available parameters

Additional information provided to the plugin iframe is included in the `window.state`
inside of the plugin script.

```javascript
const { config, modules } = window.state
const { baseUrl } = config

// modules - the list of modules of the current database
// baseUrl - url for your plugin folder - can be used to include your assets
```

### Plugin rendering
To render the plugin visualization, the iframe with basic html is generated which is
then populated with relevant scripts and styles. To render the html data, use existing
DOM Element `#app` or create your own DOM Elements.
Rendered iframe also includes `theme_DARK` or `theme_LIGHT` className on `body` to indicate the application theme used.

_Javascript Example:_
```javascript
const renderVisualization = (props) => {
const { command, data } = props;
document.getElementById('app')
.innerHTML = `
<h3>Executed command:<h3>
<p>${command}</p>
<h4>Result of the command</h4>
<p>${data}</p>
`
}

export default { renderVisualization }
```

_React Example:_
```javascript
import { render } from 'react-dom'
import App from './App'

const renderVisualization = (props) => {
const { command, status, data = '' } = props
render(
<App command={command} response={data} status={status} />,
document.getElementById('app')
)
}

// This is a required action - export the main function for execution of the visualization
export default { renderVisualization }
```


## Plugins communication
> **_Future updates:_**
Support of communication with the main application via a third-party library - _redisinsight-plugin-sdk_.
30 changes: 30 additions & 0 deletions docs/plugins/installation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Plugin installation & Usage

This document describes the guides to add `plugins` for the Workbench to RedisInsight.

## Installation guide

**Note**: While adding new plugins for Workbench, use files only from trusted
authors to avoid automatic execution of malicious code.

1. Download the plugin for the Workbench.
2. Open the `plugins` folder with the following path
* For MacOs: `<usersHomeDir>/.redisinsight-v2.0/plugins`
* For Windows: `C:\Users\{Username}\.redisinsight-v2.0\plugins`
* For Linux: `<usersHomeDir>/.redisinsight-v2.0/plugins`
3. Add the folder with plugin to the `plugins` folder

To see the uploaded plugin visualizations in the command results, reload the Workbench
page and run Redis command relevant for this visualization.


## Usage

The plugin may contain different visualizations for any Redis commands.
Below you can find a guide to see command results in the uploaded plugin visualization:

1. Open RedisInsight
2. Open a database added
3. Open the Workbench
4. Run the Redis command relevant for the plugin visualization
5. Select the plugin visualization to display results in (if this visualization has not been set by default)
9 changes: 9 additions & 0 deletions docs/plugins/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Introduction to plugins for the Workbench

Plugins allow the customization of visualizations for Redis commands executed
in the Workbench inside the RedisInsight.

## Wiki

* [Installation and Usage](installation.md)
* [Plugin Development](development.md)
1 change: 1 addition & 0 deletions electron-builder.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"dist/",
"node_modules/",
"index.html",
"splash.html",
"main.prod.js",
"main.prod.js.map",
"package.json"
Expand Down
27 changes: 27 additions & 0 deletions redisinsight/api/src/exceptions/global-exception.filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { BaseExceptionFilter } from '@nestjs/core';
import { ArgumentsHost, Logger } from '@nestjs/common';
import { Request, Response } from 'express';

export class GlobalExceptionFilter extends BaseExceptionFilter {
private staticServerLogger = new Logger('StaticServerLogger');

catch(exception: Error, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const request = ctx.getRequest<Request>();

if (/^\/(?:plugins|static)\//i.test(request.url)) {
const response = ctx.getResponse<Response>();
const statusCode = exception['statusCode'] || 500;
const message = `Error when trying to fetch ${request.url}`;

this.staticServerLogger.error(message, { ...exception } as any);
return response.status(statusCode)
.json({
statusCode,
message,
});
}

return super.catch(exception, host);
}
}
3 changes: 2 additions & 1 deletion redisinsight/api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { SwaggerModule } from '@nestjs/swagger';
import { NestApplicationOptions } from '@nestjs/common';
import * as bodyParser from 'body-parser';
import { WinstonModule } from 'nest-winston';
import { GlobalExceptionFilter } from 'src/exceptions/global-exception.filter';
import { AppModule } from './app.module';
import SWAGGER_CONFIG from '../config/swagger';
import LOGGER_CONFIG from '../config/logger';
Expand All @@ -22,7 +23,7 @@ export default async function bootstrap() {
}

const app = await NestFactory.create(AppModule, options);

app.useGlobalFilters(new GlobalExceptionFilter(app.getHttpAdapter()));
app.use(bodyParser.json({ limit: '512mb' }));
app.use(bodyParser.urlencoded({ limit: '512mb', extended: true }));
app.enableCors();
Expand Down
39 changes: 30 additions & 9 deletions redisinsight/main.dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export const getDisplayAppInTrayValue = (): boolean => {
/**
* Backend part...
*/
const port = 5000;
const port = 5001;
const launchApiServer = async () => {
try {
const detectPortConst = await detectPort(port);
Expand Down Expand Up @@ -134,19 +134,35 @@ const bootstrap = async () => {

export const windows = new Set<BrowserWindow>();

export const createWindow = async () => {
const titleSplash = 'splash';
export const createSplashScreen = async () => {
const splash = new BrowserWindow({
width: 500,
height: 200,
transparent: true,
frame: false,
resizable: false,
alwaysOnTop: true,
title: titleSplash,
});

splash.loadURL(`file://${__dirname}/splash.html`);

return splash;
};

export const createWindow = async (splash: BrowserWindow | null) => {
const RESOURCES_PATH = app.isPackaged
? path.join(process.resourcesPath, 'resources')
: path.join(__dirname, '../resources');

const getAssetPath = (...paths: string[]): string => {
return path.join(RESOURCES_PATH, ...paths);
};
const getAssetPath = (...paths: string[]): string => path.join(RESOURCES_PATH, ...paths);

let x;
let y;
const currentWindow = BrowserWindow.getFocusedWindow();
if (currentWindow) {

if (currentWindow && currentWindow?.getTitle() !== titleSplash) {
const [currentWindowX, currentWindowY] = currentWindow.getPosition();
x = currentWindowX + 24;
y = currentWindowY + 24;
Expand Down Expand Up @@ -188,8 +204,9 @@ export const createWindow = async () => {
if (process.env.START_MINIMIZED) {
newWindow.minimize();
} else {
newWindow.show();
newWindow.focus();
newWindow?.show();
newWindow?.focus();
splash?.close();
}
});

Expand Down Expand Up @@ -293,7 +310,11 @@ app.on('continue-activity-error', (event, type, error) => {
}
});

app.whenReady().then(bootstrap).then(createWindow).catch(console.log);
app.whenReady()
.then(bootstrap)
.then(createSplashScreen)
.then(createWindow)
.catch(console.log);

app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
Expand Down
Loading