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

serverInfo: show server info at MenuFooter #694

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 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
15 changes: 10 additions & 5 deletions packages/api/src/handlers/redisStats.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { parse as parseRedisInfo } from 'redis-info';
import { BullBoardRequest, ControllerHandlerReturnType, RedisStats } from '../../typings/app';
import { BullBoardRequest, ControllerHandlerReturnType, RedisStatsOptions } from '../../typings/app';
import { BaseAdapter } from '../queueAdapters/base';

function formatUptime(uptime: number) {
Expand All @@ -20,11 +20,11 @@ function formatUptime(uptime: number) {
return segments.join(', ');
}

async function getStats(queue: BaseAdapter): Promise<RedisStats> {
const redisInfoRaw = await queue.getRedisInfo();
const redisInfo = parseRedisInfo(redisInfoRaw);
async function getStats(queue: BaseAdapter): Promise<RedisStatsOptions> {
const { rawInfo, options } = await queue.getRedisInfo();
const redisInfo = parseRedisInfo(rawInfo);

return {
const stats = {
version: redisInfo.redis_version,
mode: redisInfo.redis_mode,
port: +redisInfo.tcp_port,
Expand All @@ -41,6 +41,11 @@ async function getStats(queue: BaseAdapter): Promise<RedisStats> {
blocked: +redisInfo.blocked_clients,
},
};

return {
stats,
options,
};
}

export async function redisStatsHandler({
Expand Down
3 changes: 2 additions & 1 deletion packages/api/src/queueAdapters/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
JobStatus,
QueueAdapterOptions,
QueueJob,
RedisRawInfoOptions,
Status,
} from '../../typings/app';

Expand Down Expand Up @@ -59,7 +60,7 @@ export abstract class BaseAdapter {

public abstract getName(): string;

public abstract getRedisInfo(): Promise<string>;
public abstract getRedisInfo(): Promise<RedisRawInfoOptions>;

public abstract isPaused(): Promise<boolean>;

Expand Down
9 changes: 7 additions & 2 deletions packages/api/src/queueAdapters/bull.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
JobCounts,
JobStatus,
QueueAdapterOptions,
RedisRawInfoOptions,
Status,
} from '../../typings/app';
import { STATUSES } from '../constants/statuses';
Expand All @@ -14,8 +15,12 @@ export class BullAdapter extends BaseAdapter {
super({ ...options, allowCompletedRetries: false });
}

public getRedisInfo(): Promise<string> {
return this.queue.client.info();
public getRedisInfo(): Promise<RedisRawInfoOptions> {
return this.queue.client.info()
.then((rawInfo) => ({
rawInfo,
options: this.queue.client.options,
}));
}

public getName(): string {
Expand Down
10 changes: 8 additions & 2 deletions packages/api/src/queueAdapters/bullMQ.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
JobCounts,
JobStatus,
QueueAdapterOptions,
RedisRawInfoOptions,
Status,
} from '../../typings/app';
import { STATUSES } from '../constants/statuses';
Expand All @@ -14,9 +15,14 @@ export class BullMQAdapter extends BaseAdapter {
super(options);
}

public async getRedisInfo(): Promise<string> {
public async getRedisInfo(): Promise<RedisRawInfoOptions> {
const client = await this.queue.client;
return client.info();
const rawInfo = await client.info();

return {
rawInfo,
options: client.options,
}
}

public getName(): string {
Expand Down
6 changes: 4 additions & 2 deletions packages/api/tests/api/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ describe('happy', () => {
});
});

it('should get redis stats', async () => {
it('should get redis stats and options', async () => {
const paintQueue = new Queue('Paint', { connection });
queueList.push(paintQueue);

Expand All @@ -250,8 +250,10 @@ describe('happy', () => {
.expect(200)
.then((res) => {
const responseJson = JSON.parse(res.text);
const { stats, options } = responseJson;

expect(responseJson).toHaveProperty('version', expect.stringMatching(/\d+\.\d+\.\d+/));
expect(stats).toHaveProperty('version', expect.stringMatching(/\d+\.\d+\.\d+/));
expect(options).toHaveProperty('host', connection.host);
});
});
});
Expand Down
13 changes: 13 additions & 0 deletions packages/api/typings/app.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { RedisInfo } from 'redis-info';
import { RedisOptions as RedisOpts } from 'ioredis';
import { STATUSES } from '../src/constants/statuses';
import { BaseAdapter } from '../src/queueAdapters/base';

Expand Down Expand Up @@ -88,6 +89,18 @@ export interface RedisStats {
};
}

export interface RedisOptions extends RedisOpts {}

export type RedisStatsOptions = {
stats: RedisStats,
options: RedisOptions
}

export type RedisRawInfoOptions = {
rawInfo: string,
options: RedisOptions
}

export interface AppJob {
id: QueueJobJson['id'];
name: QueueJobJson['name'];
Expand Down
10 changes: 4 additions & 6 deletions packages/ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { HeaderActions } from './components/HeaderActions/HeaderActions';
import { Loader } from './components/Loader/Loader';
import { Menu } from './components/Menu/Menu';
import { Title } from './components/Title/Title';
import { useActiveQueue } from './hooks/useActiveQueue';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We dont really need this here, each component that needs can fetch with its own hook

import { useConfirm } from './hooks/useConfirm';
import { useQueues } from './hooks/useQueues';
import { useScrollTopOnNav } from './hooks/useScrollTopOnNav';
Expand All @@ -28,8 +27,7 @@ const OverviewPageLazy = React.lazy(() =>

export const App = () => {
useScrollTopOnNav();
const { queues, actions: queueActions } = useQueues();
const activeQueue = useActiveQueue({ queues });
const { actions: queueActions } = useQueues();
const { confirmProps } = useConfirm();

useEffect(() => {
Expand All @@ -39,7 +37,7 @@ export const App = () => {
return (
<>
<Header>
<Title name={activeQueue?.name} description={activeQueue?.description} />
<Title />
<HeaderActions />
</Header>
<main>
Expand All @@ -48,7 +46,7 @@ export const App = () => {
<Switch>
<Route
path="/queue/:name/:jobId"
render={() => <JobPageLazy queue={activeQueue || null} />}
render={() => <JobPageLazy />}
/>
<Route path="/queue/:name" render={() => <QueuePageLazy />} />

Expand All @@ -58,7 +56,7 @@ export const App = () => {
<ConfirmModal {...confirmProps} />
</div>
</main>
<Menu queues={queues} />
<Menu />
<ToastContainer />
</>
);
Expand Down
8 changes: 8 additions & 0 deletions packages/ui/src/components/Menu/Menu.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@
border-left-color: #4abec7;
}

.redisOpts {
border-top: 1px solid hsl(206, 9%, 25%);
padding-top: 1rem;
text-align: center;
font-size: smaller;
color: #828e97;
}

.appVersion {
text-align: center;
}
Expand Down
11 changes: 7 additions & 4 deletions packages/ui/src/components/Menu/Menu.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { AppQueue } from '@bull-board/api/typings/app';
import cn from 'clsx';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { NavLink } from 'react-router-dom';
import { useSelectedStatuses } from '../../hooks/useSelectedStatuses';
import { useQueues } from './../../hooks/useQueues';
import { links } from '../../utils/links';
import { SearchIcon } from '../Icons/Search';
import { MenuFooter } from './MenuFooter';
import s from './Menu.module.css';

export const Menu = ({ queues }: { queues: AppQueue[] | null }) => {
export const Menu = () => {
const { t } = useTranslation();
const { queues } = useQueues();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also this way typescript infers the types and we dont need to import anything else

const selectedStatuses = useSelectedStatuses();
const [searchTerm, setSearchTerm] = useState('');

Expand Down Expand Up @@ -52,7 +54,8 @@ export const Menu = ({ queues }: { queues: AppQueue[] | null }) => {
</ul>
)}
</nav>
<div className={cn(s.appVersion, s.secondary)}>{process.env.APP_VERSION}</div>

<MenuFooter />
</aside>
);
};
21 changes: 21 additions & 0 deletions packages/ui/src/components/Menu/MenuFooter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import cn from 'clsx';
import React from 'react';
import { useRedisOptions } from '../../hooks/useRedisOptions';
import s from './Menu.module.css';

export const MenuFooter = () => {
const options = useRedisOptions();

return (
<div>
{options && (
<p className={s.redisOpts}>
{`${options.host}:${options.port}:${options.db}`}
</p>
)}
<div className={cn(s.appVersion, s.secondary)}>
{process.env.APP_VERSION}
</div>
</div>
);
};
14 changes: 8 additions & 6 deletions packages/ui/src/components/RedisStatsModal/RedisStatsModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { RedisStats } from '@bull-board/api/typings/app';
import { RedisStatsOptions } from '@bull-board/api/typings/app';
import formatBytes from 'pretty-bytes';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
Expand All @@ -8,8 +8,8 @@ import { Modal } from '../Modal/Modal';
import s from './RedisStatsModal.module.css';

const getMemoryUsage = (
used?: RedisStats['memory']['used'],
total?: RedisStats['memory']['total']
used?: RedisStatsOptions["stats"]['memory']['used'],
total?: RedisStatsOptions["stats"]['memory']['total']
) => {
if (used === undefined) {
return '-';
Expand All @@ -30,15 +30,17 @@ export interface RedisStatsModalProps {

export const RedisStatsModal = ({ open, onClose }: RedisStatsModalProps) => {
const { t } = useTranslation();
const [stats, setStats] = useState<RedisStats>(null as any);
const [info, setInfo] = useState<RedisStatsOptions>(null as any);
const api = useApi();

useInterval(() => api.getStats().then((stats) => setStats(stats)), 5000);
useInterval(() => api.getStats().then((stats) => setInfo(stats)), 5000);

if (!stats) {
if (!info) {
return null;
}

const { stats } = info;

const items = [
{
title: t('REDIS.MEMORY_USAGE'),
Expand Down
31 changes: 17 additions & 14 deletions packages/ui/src/components/Title/Title.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import React from 'react';
import s from './Title.module.css';
import { useActiveQueue } from '../../hooks/useActiveQueue';

interface TitleProps {
name?: string;
description?: string;
}
export const Title = () => {
const queue = useActiveQueue();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as hooks can be composed from other hooks, this hook doesnt need to have any arguments, it will resolve automatically which is the active queue


export const Title = ({ name, description }: TitleProps) => (
<div className={s.queueTitle}>
{!!name && (
<>
<h1 className={s.name}>{name}</h1>
{!!description && <p className={s.description}>{description}</p>}
</>
)}
</div>
);
if (!queue)
return <div/>

return (
<div className={s.queueTitle}>
{queue.name && (
<>
<h1 className={s.name}>{queue.name}</h1>
{queue.description && <p className={s.description}>{queue.description}</p>}
</>
)}
</div>
)
};
12 changes: 7 additions & 5 deletions packages/ui/src/hooks/useActiveQueue.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { AppQueue } from '@bull-board/api/typings/app';
import { useActiveQueueName } from './useActiveQueueName';
import { QueuesState } from './useQueues';
import { useQueues } from './useQueues';

export function useActiveQueue(data: Pick<QueuesState, 'queues'>): AppQueue | null {
const activeQueueName = useActiveQueueName();

if (!data.queues) {
export function useActiveQueue(): AppQueue | null {
const { queues } = useQueues();

if (!queues) {
return null;
}

const activeQueue = data.queues.find((q) => q.name === activeQueueName);
const activeQueueName = useActiveQueueName();
const activeQueue = queues.find((q) => q.name === activeQueueName);

return activeQueue || null;
}
16 changes: 16 additions & 0 deletions packages/ui/src/hooks/useRedisOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';
import { RedisOptions } from '@bull-board/api/typings/app';
import { useApi } from './useApi';

export function useRedisOptions(): RedisOptions | undefined {
const [options, setOptions] = React.useState<RedisOptions>();
const api = useApi();

React.useEffect(() => {
api.getStats().then(({ options }) => {
setOptions(options)
})
}, []);

return options;
}
7 changes: 5 additions & 2 deletions packages/ui/src/pages/JobPage/JobPage.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import { AppQueue, JobRetryStatus } from '@bull-board/api/typings/app';
import { JobRetryStatus } from '@bull-board/api/typings/app';
import cn from 'clsx';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Link, useHistory } from 'react-router-dom';
import { ArrowLeftIcon } from '../../components/Icons/ArrowLeft';
import { JobCard } from '../../components/JobCard/JobCard';
import { StickyHeader } from '../../components/StickyHeader/StickyHeader';
import { useActiveQueue } from '../../hooks/useActiveQueue';
import { useJob } from '../../hooks/useJob';
import { useSelectedStatuses } from '../../hooks/useSelectedStatuses';
import { links } from '../../utils/links';
import buttonS from '../../components/Button/Button.module.css';

export const JobPage = ({ queue }: { queue: AppQueue | null }) => {
export const JobPage = () => {
const { t } = useTranslation();
const history = useHistory();

const queue = useActiveQueue();
const { job, status, actions } = useJob();
const selectedStatuses = useSelectedStatuses();

Expand Down
Loading
Loading