Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow to pass uiConfig from server config to client #504

Merged
merged 1 commit into from
Dec 15, 2022
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: 6 additions & 1 deletion example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import * as Bull from 'bull';
import Queue3 from 'bull';
import { Queue as QueueMQ, QueueScheduler, Worker } from 'bullmq';
import express from 'express';
import { ExpressAdapter, createBullBoard, BullMQAdapter, BullAdapter } from '@bull-board/express/src';
import {
ExpressAdapter,
createBullBoard,
BullMQAdapter,
BullAdapter,
} from '@bull-board/express/src';

const redisOptions = {
port: 6379,
Expand Down
9 changes: 6 additions & 3 deletions packages/api/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import path from 'path';
import { BoardOptions, IServerAdapter } from '../typings/app';
import { errorHandler } from './handlers/error';
import { BaseAdapter } from './queueAdapters/base';
import { IServerAdapter } from '../typings/app';
import { getQueuesApi } from './queuesApi';
import path from 'path';
import { appRoutes } from './routes';
import { errorHandler } from './handlers/error';

export function createBullBoard({
queues,
serverAdapter,
options = { uiConfig: {} },
}: {
queues: ReadonlyArray<BaseAdapter>;
serverAdapter: IServerAdapter;
options?: BoardOptions;
}) {
const { bullBoardQueues, setQueues, replaceQueues, addQueue, removeQueue } = getQueuesApi(queues);
const uiBasePath = path.dirname(eval(`require.resolve('@bull-board/ui/package.json')`));
Expand All @@ -19,6 +21,7 @@ export function createBullBoard({
.setQueues(bullBoardQueues)
.setViewsPath(path.join(uiBasePath, 'dist'))
.setStaticPath('/static', path.join(uiBasePath, 'dist/static'))
.setUIConfig(options.uiConfig)
.setEntryRoute(appRoutes.entryPoint)
.setErrorHandler(errorHandler)
.setApiRoutes(appRoutes.api);
Expand Down
13 changes: 12 additions & 1 deletion packages/api/typings/app.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { RedisInfo } from 'redis-info';
import { BaseAdapter } from '../src/queueAdapters/base';
import { STATUSES } from '../src/constants/statuses';
import { BaseAdapter } from '../src/queueAdapters/base';

export type JobCleanStatus = 'completed' | 'wait' | 'active' | 'delayed' | 'failed';

Expand Down Expand Up @@ -148,6 +148,8 @@ export interface IServerAdapter {
setErrorHandler(handler: (error: Error) => ControllerHandlerReturnType): IServerAdapter;

setApiRoutes(routes: AppControllerRoute[]): IServerAdapter;

setUIConfig(config: UIConfig): IServerAdapter;
}

export interface Pagination {
Expand All @@ -159,3 +161,12 @@ export interface Pagination {
}

export type FormatterField = 'data' | 'returnValue' | 'name';

export type BoardOptions = {
uiConfig: UIConfig;
};

export type UIConfig = Partial<{
boardTitle: string;
boardLogo: { path: string; width?: number | string; height?: number | string };
}>;
17 changes: 15 additions & 2 deletions packages/express/src/ExpressAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
ControllerHandlerReturnType,
HTTPMethod,
IServerAdapter,
UIConfig,
} from '@bull-board/api/dist/typings/app';
import ejs from 'ejs';
import express, { Express, NextFunction, Request, Response, Router } from 'express';
Expand All @@ -15,6 +16,7 @@ export class ExpressAdapter implements IServerAdapter {
private basePath = '';
private bullBoardQueues: BullBoardQueues | undefined;
private errorHandler: ((error: Error) => ControllerHandlerReturnType) | undefined;
private uiConfig: UIConfig = {};

constructor() {
this.app = express();
Expand Down Expand Up @@ -88,8 +90,14 @@ export class ExpressAdapter implements IServerAdapter {

const viewHandler = (_req: Request, res: Response) => {
const basePath = this.basePath.endsWith('/') ? this.basePath : `${this.basePath}/`;

res.render(name, { basePath });
const uiConfig = JSON.stringify(this.uiConfig)
.replace(/</g, '\\u003c')
.replace(/>/g, '\\u003e');

res.render(name, {
basePath,
uiConfig,
});
};

this.app[routeDef.method](routeDef.route, viewHandler);
Expand All @@ -101,6 +109,11 @@ export class ExpressAdapter implements IServerAdapter {
return this;
}

setUIConfig(config: UIConfig = {}): ExpressAdapter {
this.uiConfig = config;
return this;
}

public getRouter(): any {
return this.app;
}
Expand Down
12 changes: 11 additions & 1 deletion packages/fastify/src/FastifyAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
BullBoardQueues,
ControllerHandlerReturnType,
IServerAdapter,
UIConfig,
} from '@bull-board/api/dist/typings/app';

import fastifyStatic from '@fastify/static';
Expand All @@ -25,6 +26,7 @@ export class FastifyAdapter implements IServerAdapter {
private viewPath: string | undefined;
private entryRoute: { method: HTTPMethods; routes: string[]; filename: string } | undefined;
private apiRoutes: Array<FastifyRouteDef> | undefined;
private uiConfig: UIConfig = {};

public setBasePath(path: string): FastifyAdapter {
this.basePath = path;
Expand Down Expand Up @@ -82,6 +84,11 @@ export class FastifyAdapter implements IServerAdapter {
return this;
}

public setUIConfig(config: UIConfig = {}): FastifyAdapter {
this.uiConfig = config;
return this;
}

public registerPlugin() {
return (fastify: FastifyInstance, _opts: { basePath: string }, next: (err?: Error) => void) => {
if (!this.statics) {
Expand Down Expand Up @@ -117,8 +124,11 @@ export class FastifyAdapter implements IServerAdapter {
url,
handler: (_req, reply) => {
const basePath = this.basePath.endsWith('/') ? this.basePath : `${this.basePath}/`;
const uiConfig = JSON.stringify(this.uiConfig)
.replace(/</g, '\\u003c')
.replace(/>/g, '\\u003e');

return reply.view(filename, { basePath });
return reply.view(filename, { basePath, uiConfig });
},
})
);
Expand Down
13 changes: 12 additions & 1 deletion packages/hapi/src/HapiAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
BullBoardQueues,
ControllerHandlerReturnType,
IServerAdapter,
UIConfig,
} from '@bull-board/api/dist/typings/app';
import { PluginBase, PluginPackage } from '@hapi/hapi';
import Vision from '@hapi/vision';
Expand All @@ -24,6 +25,7 @@ export class HapiAdapter implements IServerAdapter {
private viewPath: string | undefined;
private entryRoute: AppViewRoute | undefined;
private apiRoutes: HapiRouteDef[] | undefined;
private uiConfig: UIConfig = {};

public setBasePath(path: string): HapiAdapter {
this.basePath = path;
Expand Down Expand Up @@ -76,6 +78,11 @@ export class HapiAdapter implements IServerAdapter {
return this;
}

public setUIConfig(config: UIConfig = {}): HapiAdapter {
this.uiConfig = config;
return this;
}

public registerPlugin(): PluginBase<any> & PluginPackage {
return {
pkg: require('../package.json'),
Expand Down Expand Up @@ -127,7 +134,11 @@ export class HapiAdapter implements IServerAdapter {
handler: (_request, h) => {
const { name } = handler();
const basePath = this.basePath.endsWith('/') ? this.basePath : `${this.basePath}/`;
return h.view(name, { basePath });
const uiConfig = JSON.stringify(this.uiConfig)
.replace(/</g, '\\u003c')
.replace(/>/g, '\\u003e');

return h.view(name, { basePath, uiConfig });
},
})
);
Expand Down
13 changes: 12 additions & 1 deletion packages/koa/src/KoaAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
BullBoardQueues,
ControllerHandlerReturnType,
IServerAdapter,
UIConfig,
} from '@bull-board/api/dist/typings/app';

import Koa from 'koa';
Expand All @@ -21,6 +22,7 @@ export class KoaAdapter implements IServerAdapter {
private viewPath: string | undefined;
private entryRoute: AppViewRoute | undefined;
private apiRoutes: AppControllerRoute[] | undefined;
private uiConfig: UIConfig = {};

public setBasePath(path: string): KoaAdapter {
this.basePath = path;
Expand Down Expand Up @@ -60,6 +62,11 @@ export class KoaAdapter implements IServerAdapter {
return this;
}

public setUIConfig(config: UIConfig = {}): KoaAdapter {
this.uiConfig = config;
return this;
}

public registerPlugin(options: Partial<{ mount: string }> = { mount: this.basePath }) {
if (!this.statics) {
throw new Error(`Please call 'setStaticPath' before using 'registerPlugin'`);
Expand Down Expand Up @@ -110,7 +117,11 @@ export class KoaAdapter implements IServerAdapter {
router[method](path, async (ctx) => {
const { name } = handler();
const basePath = this.basePath.endsWith('/') ? this.basePath : `${this.basePath}/`;
await (ctx as any).render(name, { basePath });
const uiConfig = JSON.stringify(this.uiConfig)
.replace(/</g, '\\u003c')
.replace(/>/g, '\\u003e');

await (ctx as any).render(name, { basePath, uiConfig });
});
});

Expand Down
11 changes: 8 additions & 3 deletions packages/ui/src/components/Header/Header.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,17 @@
border-bottom: 1px solid rgba(0, 0, 0, 0.4);
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.1);
z-index: 2;
display: flex;
justify-content: center;
}

.header > .logo > img {
width: 1.2em;
.header > .logo > .img {
margin-right: 0.3em;
margin-bottom: -0.2em;
}

.header > .logo > .img.default {
width: 1.2em;
margin-bottom: 0.2em;
}

.header .content {
Expand Down
36 changes: 25 additions & 11 deletions packages/ui/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
import cn from 'clsx';
import React, { PropsWithChildren } from 'react';
import { useUIConfig } from '../../hooks/useUIConfig';
import s from './Header.module.css';
import { getStaticPath } from '../../utils/getStaticPath';

export const Header = ({ children }: PropsWithChildren<any>) => (
<header className={s.header}>
<div className={s.logo}>
<img src={getStaticPath('/images/logo.svg')} alt="Bull Dashboard" />
Bull Dashboard
</div>
<div className={cn(s.content, { [s.positionRight]: React.Children.count(children) === 1 })}>
{children}
</div>
</header>
);
export const Header = ({ children }: PropsWithChildren<any>) => {
const uiConfig = useUIConfig();
const logoPath = uiConfig.boardLogo?.path ?? getStaticPath('/images/logo.svg');
const boardTitle = uiConfig.boardTitle ?? 'Bull Dashboard';
return (
<header className={s.header}>
<div className={s.logo}>
{!!logoPath && (
<img
src={logoPath}

Check warning

Code scanning / CodeQL

DOM text reinterpreted as HTML

[DOM text](1) is reinterpreted as HTML without escaping meta-characters.
className={cn(s.img, { [s.default]: !uiConfig.boardLogo })}
width={uiConfig.boardLogo?.width}
height={uiConfig.boardLogo?.height}
alt={boardTitle}
/>
)}
{boardTitle}
</div>
<div className={cn(s.content, { [s.positionRight]: React.Children.count(children) === 1 })}>
{children}
</div>
</header>
);
};
8 changes: 8 additions & 0 deletions packages/ui/src/hooks/useUIConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { UIConfig } from '@bull-board/api/dist/typings/app';
import React, { useContext } from 'react';

export const UIConfigContext = React.createContext<UIConfig>(null as any);

export function useUIConfig() {
return useContext(UIConfigContext);
}
1 change: 1 addition & 0 deletions packages/ui/src/index.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<link href="https://fonts.googleapis.com/css2?family=Ubuntu:wght@300;400;500&display=swap" rel="stylesheet">
</head>
<body>
<script id="__UI_CONFIG__" type="application/json"><%= uiConfig %></script>
<div id="root">Loading...</div>
</body>
</html>
10 changes: 7 additions & 3 deletions packages/ui/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,23 @@ import { BrowserRouter } from 'react-router-dom';
import { App } from './components/App';
import { ApiContext } from './hooks/useApi';
import './index.css';
import { UIConfigContext } from './hooks/useUIConfig';
import { Api } from './services/Api';
import './theme.css';
import './toastify.css';

const basePath = ((window as any).__basePath__ =
document.head.querySelector('base')?.getAttribute('href') || '');
const api = new Api({ basePath });
const uiConfig = JSON.parse(document.getElementById('__UI_CONFIG__')?.textContent || '{}');

render(
<BrowserRouter basename={basePath}>
<ApiContext.Provider value={api}>
<App />
</ApiContext.Provider>
<UIConfigContext.Provider value={uiConfig}>
<ApiContext.Provider value={api}>
<App />
</ApiContext.Provider>
</UIConfigContext.Provider>
</BrowserRouter>,
document.getElementById('root')
);
1 change: 1 addition & 0 deletions packages/ui/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ module.exports = {
template: './src/index.ejs',
templateParameters: {
basePath,
uiConfig: '<%- uiConfig %>',
},
inject: 'body',
}),
Expand Down