diff --git a/src/components/Connections.tsx b/src/components/Connections.tsx index 4475c55..aeb245f 100644 --- a/src/components/Connections.tsx +++ b/src/components/Connections.tsx @@ -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(5); + return (
+

Maximum log level + +

Maximum log level

+

+ The maximum log level determines which publication points are + shown in the RRDP and rsync tables. By selecting WARN, only + publication points with an ERROR and/or WARN log message + are shown. +

+

+ The log messages that can be shown also depends on the log-level setting in your Routinator config. If your log-level is + set to WARN, the UI will never show any INFO messages. +

+
+

+ + + + + +

Rrdp

- +

Rsync

- +

Status

diff --git a/src/components/LogMessages.tsx b/src/components/LogMessages.tsx new file mode 100644 index 0000000..3f0c418 --- /dev/null +++ b/src/components/LogMessages.tsx @@ -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(false); + + let logLevel = lowestLogLevel(issues); + return ( + <> + setShow(!show)}> +
+ {logLevel.text.slice(0, 1)} +
+ + {text} + +
+ {createPortal( + <> +
setShow(false)} + > +
{show && <> +

{type} log messages

+
+ {issues.map(issue =>
+
+ {issue.level} +
+ {issue.messages} +
)} +
+ } +
+
+ , + document.body, + 'log-messages' + )} + + ); +} diff --git a/src/components/connections/RrdpTable.tsx b/src/components/connections/RrdpTable.tsx index 6e3979f..e0e6a44 100644 --- a/src/components/connections/RrdpTable.tsx +++ b/src/components/connections/RrdpTable.tsx @@ -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; @@ -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(null); let values = Object.entries(status.rrdp); @@ -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), @@ -56,12 +65,17 @@ export default function RrdpTable() { + {values.length == 0 && +

No entries match the filter

+ } {values.map(([key, rrdp]: [string, Rrdp]) => ( - - {key} - + {!rrdp.issues && {key}} + {rrdp.issues && } diff --git a/src/components/connections/RsyncTable.tsx b/src/components/connections/RsyncTable.tsx index f783d22..e579aef 100644 --- a/src/components/connections/RsyncTable.tsx +++ b/src/components/connections/RsyncTable.tsx @@ -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(null); let values = Object.entries(status.rsync); @@ -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), @@ -48,13 +55,18 @@ export default function RsyncTable() { + {values.length == 0 && +

No entries match the filter

+ } {values.map( ([key, rsync]: [string, Rsync]) => ( - - {key} - + {!rsync.issues && {key}} + {rsync.issues && } diff --git a/src/core/util.ts b/src/core/util.ts index e371ec4..30eee52 100644 --- a/src/core/util.ts +++ b/src/core/util.ts @@ -1,4 +1,5 @@ import en from '../locales/en.json'; +import { Issue, PubPointIssue } from '../types'; export function t(key: string): string { return ( @@ -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'); diff --git a/src/style/connections.css b/src/style/connections.css index 5f7b6ae..33de3c3 100644 --- a/src/style/connections.css +++ b/src/style/connections.css @@ -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; } diff --git a/src/style/index.css b/src/style/index.css index ba4c4ed..d5fe50e 100644 --- a/src/style/index.css +++ b/src/style/index.css @@ -12,3 +12,4 @@ @import './prefix-check-sidebar.css'; @import './prefix-check.css'; @import './help.css'; +@import './log-messages.css'; diff --git a/src/style/log-messages.css b/src/style/log-messages.css new file mode 100644 index 0000000..2d8ccce --- /dev/null +++ b/src/style/log-messages.css @@ -0,0 +1,108 @@ +.log-overlay { + visibility: hidden; + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + cursor: pointer; + z-index: 10; + transition: all 200ms ease-in-out; + opacity: 0; +} + +.log-overlay.visible { + visibility: visible; + opacity: 1; +} + +.log-overlay .bar { + position: absolute; + right: -100%; + width: 85vw; + height: 100%; + max-width: 90%; + line-height: 1.4; + background: var(--white); + padding: 1.5rem; + color: var(--dark); + overflow-y: auto; + transition: all 200ms ease-in-out; +} + +.log-overlay.visible .bar { + right: 0; +} + +.log-overlay .bar h3, +.log-overlay .bar h2 { + font-weight: 500; + font-size: 1rem; + margin-top: 1rem; + margin-bottom: 0.5rem; +} + +.log-overlay .bar h2 { + font-size: 1.2rem; +} + +.log-overlay .bar p { + margin-bottom: 0.5rem; +} + +.log-overlay .bar ul { + list-style: disc; + padding-left: 1.5rem; + margin-bottom: 1rem; +} + + +.log-messages { + width: 100%; +} + +.log-messages div { + padding: 0.1rem 0; +} + +.log-label { + border-radius: 0.3rem; + padding: 0.2rem 0.4rem; + font-weight: bold; + font-family: monospace, monospace; +} + +.log-label-container { + display: inline-block; + margin-right: 0.5rem; +} + +.log-label.TRACE { + background-color: white; + color: black; +} + +.log-label.DEBUG { + background-color: #EEEEEE; + color: black; +} + +.log-label.INFO { + background-color: #1976D2; + color: white; +} + +.log-label.WARN { + background-color: #FFA000; + color: white; +} + +.log-label.ERROR { + background-color: #E64A19; + color: white; +} + +.log-messages .message { + font-family: monospace; +} \ No newline at end of file diff --git a/src/style/scroll-table.css b/src/style/scroll-table.css index ab48f7a..e4d9556 100644 --- a/src/style/scroll-table.css +++ b/src/style/scroll-table.css @@ -6,7 +6,7 @@ } .scroll-table > div { - padding: 1rem; + padding: 0.5rem; position: relative; } @@ -64,6 +64,10 @@ background-color: var(--grey-99); } +.scroll-table td p { + text-align: center; +} + .scroll-table tr:nth-child(even) td { background-color: var(--grey-98); } diff --git a/src/types.ts b/src/types.ts index 3ffddb5..e36e9bc 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,6 +11,7 @@ export interface RoutinatorStatus { vrpsAddedLocally: number; rsync: { [key: string]: Rsync }; rrdp: { [key: string]: Rrdp }; + pubPointIssues: { [key: string]: PubPointIssue[] } rtr: Rtr; http: HTTP; } @@ -90,11 +91,23 @@ export interface Rrdp { session?: null | string; delta?: boolean; snapshot_reason?: null; + issues?: Issue[]; } export interface Rsync { status: number; duration: number; + issues?: Issue[]; +} + +export interface Issue { + level: string; + messages: string; +} + +export interface PubPointIssue { + level: string; + message: string; } export interface Rtr {