Skip to content
Open
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
75 changes: 72 additions & 3 deletions src/components/Connections.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,86 @@
import React from 'react';
import React, { useState } from 'react';
import RrdpTable from './connections/RrdpTable';
import RsyncTable from './connections/RsyncTable';
import Status from './connections/Status';
import Help from './Help';

export default function Connections() {
const [level, setLevel] = useState<number>(5);

return (
<div id="connections">
<h3>Maximum log level
<Help>
<h2>Maximum log level</h2>
<p>
The maximum log level determines which publication points are
shown in the RRDP and rsync tables. By selecting <span className='log-label WARN'>WARN</span>, only
publication points with an <span className='log-label ERROR'>ERROR</span> and/or <span className='log-label WARN'>WARN</span> log message
are shown.
</p>
<p>
The log messages that can be shown also depends on the <code>log-level</code> setting in your Routinator config. If your <code>log-level</code> is
set to <code>WARN</code>, the UI will never show any <span className='log-label INFO'>INFO</span> messages.
</p>
</Help>
</h3>

<label>
<input
type='radio'
name='log-level'
value='5'
checked={level === 5}
onChange={e => setLevel(Number(e.target.value))}
/>
Display all
</label>
<label>
<input
type='radio'
name='log-level'
value='0'
checked={level === 0}
onChange={e => setLevel(Number(e.target.value))}
/>
<span className='log-label ERROR'>ERROR</span>
</label>
<label>
<input
type='radio'
name='log-level'
value='1'
checked={level === 1}
onChange={e => setLevel(Number(e.target.value))}
/>
<span className='log-label WARN'>WARN</span>
</label>
<label>
<input
type='radio'
name='log-level'
value='2'
checked={level === 2}
onChange={e => setLevel(Number(e.target.value))}
/>
<span className='log-label INFO'>INFO</span>
</label>
<label>
<input
type='radio'
name='log-level'
value='3'
checked={level === 3}
onChange={e => setLevel(Number(e.target.value))}
/>
<span className='log-label DEBUG'>DEBUG</span>
</label>
<h3>Rrdp</h3>
<RrdpTable />
<RrdpTable level={level} />
<div className="side-by-side">
<div>
<h3>Rsync</h3>
<RsyncTable />
<RsyncTable level={level} />
</div>
<div>
<h3>Status</h3>
Expand Down
51 changes: 51 additions & 0 deletions src/components/LogMessages.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, { useState } from 'react';
import { createPortal } from 'react-dom';
import { Issue } from '../types';
import { lowestLogLevel } from '../core/util';

interface LogMessagesProps {
text: string;
issues: Issue[];
type: string;
}

export default function LogMessages({ text, issues, type }: LogMessagesProps) {
const [show, setShow] = useState<boolean>(false);

let logLevel = lowestLogLevel(issues);
return (
<>
<span onClick={() => setShow(!show)}>
<div className='log-label-container'>
<span className={`log-label ${logLevel.text}`}>{logLevel.text.slice(0, 1)}</span>
</div>
<a href='#'>
{text}
</a>
</span>
{createPortal(
<>
<div
className={`log-overlay ${show ? 'visible' : 'hidden'}`}
onClick={() => setShow(false)}
>
<div className="bar">{show && <>
<h2>{type} log messages</h2>
<div className='log-messages'>
{issues.map(issue => <div className=''>
<div className='log-label-container'>
<span className={`log-label ${issue.level}`}>{issue.level}</span>
</div>
<span className='message'>{issue.messages}</span>
</div>)}
</div>
</>}
</div>
</div>
</>,
document.body,
'log-messages'
)}
</>
);
}
24 changes: 19 additions & 5 deletions src/components/connections/RrdpTable.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { useContext, useState } from 'react';
import { t, tryFormatNumber } from '../../core/util';
import { lowestLogLevel, t, tryFormatNumber } from '../../core/util';
import { Rrdp } from '../../types';
import Duration from './Duration';
import { StatusContext } from '../../hooks/useStatus';
import LogMessages from '../LogMessages';

type RrdpKey = keyof Rrdp;

Expand All @@ -17,7 +18,11 @@ const RRDP_FIELDS: RrdpKey[] = [
'session',
];

export default function RrdpTable() {
interface RrdpTableProps {
level: number;
}

export default function RrdpTable({ level }: RrdpTableProps) {
const { status } = useContext(StatusContext);
const [sort, setSort] = useState<RrdpKey | null>(null);
let values = Object.entries(status.rrdp);
Expand All @@ -31,6 +36,10 @@ export default function RrdpTable() {
}
});

if (level !== 5) {
values = values.filter(x => x[1].issues && lowestLogLevel(x[1].issues).level <= level);
}


const maxDuration = Object.values(status.rrdp).reduce(
(acc, i) => Math.max(acc, i.duration),
Expand All @@ -56,12 +65,17 @@ export default function RrdpTable() {
</tr>
</thead>
<tbody>
{values.length == 0 && <tr>
<td colSpan={9}><p>No entries match the filter</p></td>
</tr>}
{values.map(([key, rrdp]: [string, Rrdp]) => (
<tr key={key}>
<th role="column" title={key}>
<a href={key} target="_blank" rel="noreferrer">
{key}
</a>
{!rrdp.issues && <span>{key}</span>}
{rrdp.issues && <LogMessages
text={key}
issues={rrdp.issues}
type='RRDP' />}
</th>
<td>
<Duration value={rrdp.duration} max={maxDuration} />
Expand Down
24 changes: 18 additions & 6 deletions src/components/connections/RsyncTable.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import React, { useContext, useState } from 'react';
import { t, tryFormatNumber } from '../../core/util';
import { lowestLogLevel, t, tryFormatNumber } from '../../core/util';
import { Rsync } from '../../types';
import Duration from './Duration';
import { StatusContext } from '../../hooks/useStatus';
import LogMessages from '../LogMessages';

type RsyncKey = keyof Rsync;

const RSYNC_FIELDS: RsyncKey[] = ['duration', 'status'];

export default function RsyncTable() {
interface RsyncTableProps {
level: number;
}

export default function RsyncTable({ level }: RsyncTableProps) {
const { status } = useContext(StatusContext);
const [sort, setSort] = useState<RsyncKey | null>(null);
let values = Object.entries(status.rsync);
Expand All @@ -22,7 +27,9 @@ export default function RsyncTable() {
}
});


if (level !== 5) {
values = values.filter(x => x[1].issues && lowestLogLevel(x[1].issues).level <= level);
}

const maxDuration = Object.values(status.rsync).reduce(
(acc, i) => Math.max(acc, i.duration),
Expand All @@ -48,13 +55,18 @@ export default function RsyncTable() {
</tr>
</thead>
<tbody>
{values.length == 0 && <tr>
<td colSpan={3}><p>No entries match the filter</p></td>
</tr>}
{values.map(
([key, rsync]: [string, Rsync]) => (
<tr key={key}>
<th role="column" title={key}>
<a href={key} target="_blank" rel="noreferrer">
{key}
</a>
{!rsync.issues && <span>{key}</span>}
{rsync.issues && <LogMessages
text={key}
issues={rsync.issues}
type='rsync' />}
</th>
<td>
<Duration value={rsync.duration} max={maxDuration} />
Expand Down
26 changes: 25 additions & 1 deletion src/core/util.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import en from '../locales/en.json';
import { Issue, PubPointIssue } from '../types';

export function t(key: string): string {
return (
Expand Down Expand Up @@ -54,11 +55,34 @@ export function arrayToCommaSeperated(arr: string[]): string {
}

export function tryFormatNumber(
v: string | number | boolean | null | undefined
v: string | number | boolean | any | null | undefined
) {
return Number.isInteger(v) ? (v || 0).toLocaleString('en') : v;
}

export function pubPointIssueToIssue(input: PubPointIssue): Issue {
return {
level: input.level,
messages: input.message
}
}

export function lowestLogLevel(input: Issue[]) {
const logLevels = ["ERROR", "WARN", "INFO", "DEBUG", "TRACE", "NONE"];
if (input.length == 0) {
return {
level: 5,
text: logLevels[5]
}
}
let logLevel = Math.min(...input.map(issue => logLevels.indexOf(issue.level)));
let logLevelText = logLevels[logLevel];
return {
level: logLevel,
text: logLevelText
}
}

export function timeAgo(input: Date | string) {
const date = input instanceof Date ? input : new Date(input);
const formatter = new Intl.RelativeTimeFormat('en');
Expand Down
13 changes: 12 additions & 1 deletion src/style/connections.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,19 @@
margin: 1rem 0 0 0;
}

#connections .help {
width: 1rem;
height: 1rem;
background-color: var(--grey-dark);
margin-top: -0.2rem;
}

#connections label input {
margin-right: 0.3rem;
}

#rrdp {
max-height: 20rem;
max-height: 30rem;
max-width: 120rem;
}

Expand Down
1 change: 1 addition & 0 deletions src/style/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@
@import './prefix-check-sidebar.css';
@import './prefix-check.css';
@import './help.css';
@import './log-messages.css';
Loading
Loading