diff --git a/.changeset/kind-glasses-thank.md b/.changeset/kind-glasses-thank.md new file mode 100644 index 0000000..7d0e22b --- /dev/null +++ b/.changeset/kind-glasses-thank.md @@ -0,0 +1,5 @@ +--- +'@envyjs/webui': patch +--- + +Align styles with new design diff --git a/docs/images/envy-example.png b/docs/images/envy-example.png index 9fbae6a..dca9301 100644 Binary files a/docs/images/envy-example.png and b/docs/images/envy-example.png differ diff --git a/packages/webui/src/collector/CollectorClient.ts b/packages/webui/src/collector/CollectorClient.ts index 5d0e7df..9ccf615 100644 --- a/packages/webui/src/collector/CollectorClient.ts +++ b/packages/webui/src/collector/CollectorClient.ts @@ -22,6 +22,8 @@ type WebSocketClientOptions = { const INTERNAL_HTTP_TIMEOUT = 120 * 1000; const initialTraceMap = () => { + // ignore coverage for this line since it's only used in dev + // istanbul ignore next return process.env.DEMO === 'true' ? mockTraceCollection() : new Map(); }; diff --git a/packages/webui/src/components/Authorization.tsx b/packages/webui/src/components/Authorization.tsx index 217c74b..20a86f2 100644 --- a/packages/webui/src/components/Authorization.tsx +++ b/packages/webui/src/components/Authorization.tsx @@ -83,7 +83,7 @@ export default function Authorization({ value }: AuthorizationProps) { return (
setTokenState(TokenState.Expanded)} >
@@ -98,14 +98,14 @@ export default function Authorization({ value }: AuthorizationProps) { return {`${type} ${token}`}; case TokenState.Decoded: return ( -
+
{decodedToken}
); } })()} {tokenState !== TokenState.Minimal && ( -
+
<>
)} {!value && focusKey && specialKey && ( - + {specialKey} {focusKey} @@ -84,7 +87,7 @@ function Input({ className, onChange, Icon, focusKey, type, ...props }: InputPro {!!value && ( diff --git a/packages/webui/src/components/KeyValueList.tsx b/packages/webui/src/components/KeyValueList.tsx index 0576e47..53fd3c3 100644 --- a/packages/webui/src/components/KeyValueList.tsx +++ b/packages/webui/src/components/KeyValueList.tsx @@ -8,7 +8,7 @@ export default function KeyValueList({ values }: KeyValueList) { if (!values.length) return null; return ( - +
{values.map(([k, v]) => { let value: React.ReactNode = v; @@ -25,8 +25,8 @@ export default function KeyValueList({ values }: KeyValueList) { } return ( - - + + ); })} diff --git a/packages/webui/src/components/Label.stories.ts b/packages/webui/src/components/Label.stories.ts deleted file mode 100644 index c24560b..0000000 --- a/packages/webui/src/components/Label.stories.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import Label from './Label'; - -const meta = { - title: 'Components/Label', - component: Label, - parameters: { - layout: 'centered', - }, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const Standard: Story = { - args: { - label: 'Some Label', - }, -}; diff --git a/packages/webui/src/components/Label.test.tsx b/packages/webui/src/components/Label.test.tsx deleted file mode 100644 index 41ce952..0000000 --- a/packages/webui/src/components/Label.test.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { cleanup, render } from '@testing-library/react'; - -import Label from './Label'; - -describe('Label', () => { - afterEach(() => { - cleanup(); - }); - - it('should render without error', () => { - render(
{k}{value}{k}{value}
- {presentationData.map(({ name, color, duration, offset, percentage }, idx) => ( - - - + +
{name} + {presentationData.map(({ name, color, duration, offset, percentage }) => ( +
{name}
{ if (counterElRef.current) { @@ -78,21 +80,21 @@ export default function TraceDetail() { } return ( -
-
+
+
-
- +
+
- {method} +
{method}
{requestAborted && ( - + Aborted )} {statusCode && ( - + {httpStatusLabel} )} @@ -104,7 +106,7 @@ export default function TraceDetail() {
-
+
{url}
@@ -115,9 +117,12 @@ export default function TraceDetail() {
-
+
-
+
@@ -164,31 +169,30 @@ export default function TraceDetail() {
{showResponseHeaders && ( - <> -
- -
- -
- {trace.http?.timingsBlockedByCors && ( - - )} - {trace.http?.timings && ( -
- -
- )} -
- +
+ +
+ )} + {showTiming && ( +
+ {trace.http?.timingsBlockedByCors && ( + + )} + {trace.http?.timings && ( +
+ +
+ )} +
)} @@ -213,12 +217,3 @@ export default function TraceDetail() {
); } - -function statusCodeStyle(code: number) { - let style = 'bg-gray-500'; - if (code >= 500) style = 'bg-purple-500'; - else if (code >= 400) style = 'bg-red-500'; - else if (code >= 300) style = 'bg-yellow-500'; - else if (code >= 200) style = 'bg-green-500'; - return style; -} diff --git a/packages/webui/src/components/ui/TraceList.tsx b/packages/webui/src/components/ui/TraceList.tsx index 8c0ccd1..8c87874 100644 --- a/packages/webui/src/components/ui/TraceList.tsx +++ b/packages/webui/src/components/ui/TraceList.tsx @@ -46,7 +46,7 @@ export default function TraceList({ autoScroll: initialAutoScroll = true }: Trac const hasTraces = data.length > 0; return ( -
+
) : ( -
-
- Method +
+
+ Method Request Time
-
+
{data.map(trace => ( ))} @@ -71,8 +71,8 @@ export default function TraceList({ autoScroll: initialAutoScroll = true }: Trac )}
{hasTraces && ( -
-
+
+
Traces: {data.length}
diff --git a/packages/webui/src/components/ui/TraceListHeader.tsx b/packages/webui/src/components/ui/TraceListHeader.tsx index 36651eb..2ea0652 100644 --- a/packages/webui/src/components/ui/TraceListHeader.tsx +++ b/packages/webui/src/components/ui/TraceListHeader.tsx @@ -4,7 +4,7 @@ type TraceListHeaderProps = React.HtmlHTMLAttributes; export default function TraceListHeader({ className, children, ...props }: TraceListHeaderProps) { return ( -
+
{children}
); diff --git a/packages/webui/src/components/ui/TraceListPlaceholder.tsx b/packages/webui/src/components/ui/TraceListPlaceholder.tsx index a85144b..43c874c 100644 --- a/packages/webui/src/components/ui/TraceListPlaceholder.tsx +++ b/packages/webui/src/components/ui/TraceListPlaceholder.tsx @@ -14,7 +14,7 @@ export default function TraceListPlaceholder() { return (
{message}
diff --git a/packages/webui/src/components/ui/TraceListRow.test.tsx b/packages/webui/src/components/ui/TraceListRow.test.tsx index ae44c41..ef2e0b0 100644 --- a/packages/webui/src/components/ui/TraceListRow.test.tsx +++ b/packages/webui/src/components/ui/TraceListRow.test.tsx @@ -7,6 +7,9 @@ import { Trace } from '@/types'; import TraceListRow from './TraceListRow'; jest.mock('@/components', () => ({ + Badge: function Badge({ children }: { children: React.ReactNode }) { + return <>{children}; + }, Loading: function Loading() { return <>Mock Loading component; }, @@ -27,26 +30,6 @@ describe('TraceListRow', () => { }); describe('method column', () => { - it('should display HTTP method only if no response status code exists', () => { - setUseApplicationData({}); - - const trace = { - id: '1', - timestamp: 0, - http: { - method: 'GET', - statusCode: undefined, - } as Trace['http'], - }; - - const { getByTestId } = render(); - const methodData = getByTestId('column-data-method-cell'); - const statusCodeData = getByTestId('column-data-code-cell'); - - expect(methodData).toHaveTextContent('GET'); - expect(statusCodeData).toBeEmptyDOMElement(); - }); - it('should display HTTP method and status code if response exists', () => { const trace = { id: '1', @@ -59,10 +42,8 @@ describe('TraceListRow', () => { const { getByTestId } = render(); const methodData = getByTestId('column-data-method-cell'); - const statusCodeData = getByTestId('column-data-code-cell'); - expect(methodData).toHaveTextContent('POST'); - expect(statusCodeData).toHaveTextContent('204'); + expect(methodData).toHaveTextContent('POST 204'); }); it('should display HTTP method and aborted status if response exists and state is aborted', () => { @@ -78,10 +59,8 @@ describe('TraceListRow', () => { const { getByTestId } = render(); const methodData = getByTestId('column-data-method-cell'); - const statusCodeData = getByTestId('column-data-code-cell'); - expect(methodData).toHaveTextContent('POST'); - expect(statusCodeData).toHaveTextContent('Aborted'); + expect(methodData).toHaveTextContent('POST Aborted'); }); it('should render nothing for method and status if `http` property of trace is not defined', () => { @@ -93,10 +72,8 @@ describe('TraceListRow', () => { const { getByTestId } = render(); const methodData = getByTestId('column-data-method-cell'); - const statusCodeData = getByTestId('column-data-code-cell'); - expect(methodData).toBeEmptyDOMElement(); - expect(statusCodeData).toBeEmptyDOMElement(); + expect(methodData).toHaveTextContent(''); }); }); @@ -169,33 +146,6 @@ describe('TraceListRow', () => { }); describe('trace row colours', () => { - const scenarios = [ - { statusCode: 500, bgColor: 'purple-500' }, - { statusCode: 404, bgColor: 'red-500' }, - { statusCode: 300, bgColor: 'yellow-500' }, - { statusCode: 200, bgColor: 'green-500' }, - ]; - - it.each(scenarios)('should have $bgColor left border for HTTP $statusCode responses', ({ statusCode, bgColor }) => { - const trace = { - id: '1', - timestamp: 0, - http: { - method: 'GET', - statusCode, - } as Trace['http'], - }; - - const { getByTestId } = render(); - const badge = getByTestId('column-data-status-cell'); - - if (bgColor) { - expect(badge).toHaveClass(`border-l-${bgColor}`); - } else { - expect(badge).toHaveClass('border-gray-300'); - } - }); - it('should render selected trace row correctly', () => { const trace = { id: '1', @@ -212,7 +162,7 @@ describe('TraceListRow', () => { const { getByTestId } = render(); const traceRow = getByTestId('trace'); - expect(traceRow).toHaveClass('bg-green-100'); + expect(traceRow).toHaveClass('bg-manatee-400'); }); }); diff --git a/packages/webui/src/components/ui/TraceListRow.tsx b/packages/webui/src/components/ui/TraceListRow.tsx index 620e697..5b0ef7c 100644 --- a/packages/webui/src/components/ui/TraceListRow.tsx +++ b/packages/webui/src/components/ui/TraceListRow.tsx @@ -1,10 +1,11 @@ import { HttpRequestState } from '@envyjs/core'; -import { Loading } from '@/components'; +import { Badge, Loading } from '@/components'; import useApplication from '@/hooks/useApplication'; import { ListDataComponent } from '@/systems'; import { Trace } from '@/types'; import { tw } from '@/utils'; +import { badgeStyle } from '@/utils/styles'; import TraceListRowCell from './TraceListRowCell'; @@ -17,26 +18,20 @@ export default function TraceListRow({ trace }: { trace: Trace }) { key={trace.id} onClick={() => setSelectedTrace(trace.id)} className={tw( - 'table-row h-16 hover:bg-green-100 hover:cursor-pointer hover:shadow', - rowStyle(trace), - trace.id === selectedTraceId && 'bg-green-100', + 'table-row h-11 hover:bg-apple-200 hover:cursor-pointer hover:text-apple-900 even:bg-manatee-200 text-manatee-800', + trace.http?.state === HttpRequestState.Sent && 'text-manatee-500', + trace.id === selectedTraceId && 'bg-manatee-400 text-manatee-950', )} > - -
- {trace.http?.method.toUpperCase()} -
-
- {getResponseStatus(trace)} -
+ + + {trace.http?.method.toUpperCase()} {getResponseStatus(trace)} + - + {formatRequestDuration(trace)}
@@ -45,37 +40,10 @@ export default function TraceListRow({ trace }: { trace: Trace }) { function getResponseStatus(trace: Trace) { const { statusCode, state } = trace.http || {}; - if (state === HttpRequestState.Aborted) return 'Aborted'; - return statusCode; } -function indicatorStyle(trace: Trace) { - const { statusCode, state } = trace.http || {}; - - if (state === HttpRequestState.Aborted) return 'border-l-gray-500'; - - if (statusCode) { - if (statusCode >= 500) return 'border-l-purple-500'; - else if (statusCode >= 400) return 'border-l-red-500'; - else if (statusCode >= 300) return 'border-l-yellow-500'; - else if (statusCode >= 200) return 'border-l-green-500'; - } -} - -function rowStyle(trace: Trace) { - const { statusCode, state } = trace.http || {}; - - if (state === HttpRequestState.Aborted) return 'bg-gray-300'; - - if (statusCode) { - if (statusCode >= 500) return 'bg-purple-200'; - else if (statusCode >= 400) return 'bg-red-200'; - else if (statusCode >= 300) return 'bg-yellow-200'; - } -} - function formatRequestDuration(trace: Trace) { return trace.http?.duration ? `${(trace.http.duration / 1000).toFixed(2)}s` : ; } diff --git a/packages/webui/src/components/ui/TraceListRowCell.tsx b/packages/webui/src/components/ui/TraceListRowCell.tsx index 2177a38..3fc1fa2 100644 --- a/packages/webui/src/components/ui/TraceListRowCell.tsx +++ b/packages/webui/src/components/ui/TraceListRowCell.tsx @@ -6,7 +6,7 @@ export default function TraceListRowCell({ className, children, ...props }: Trac return (
{systemName} {hostName && ( - + {hostName} )} {pathValue} {data && ( - + {data} )} diff --git a/packages/webui/src/hooks/useFeatureFlags.ts b/packages/webui/src/hooks/useFeatureFlags.ts index d5b251d..0766331 100644 --- a/packages/webui/src/hooks/useFeatureFlags.ts +++ b/packages/webui/src/hooks/useFeatureFlags.ts @@ -1,5 +1,5 @@ export default function useFeatureFlags() { return { - enableThemeSwitcher: true, + enableThemeSwitcher: false, }; } diff --git a/packages/webui/src/styles/allotment.css b/packages/webui/src/styles/allotment.css index 8603833..758934e 100644 --- a/packages/webui/src/styles/allotment.css +++ b/packages/webui/src/styles/allotment.css @@ -1,5 +1,5 @@ :root { - --separator-border: rgba(128, 128, 128, 0.35); + --separator-border: rgba(181, 185, 196, 1); } .allotment-module_splitView__L-yRc { diff --git a/packages/webui/src/styles/base.css b/packages/webui/src/styles/base.css index a0a1bb6..f4d738e 100644 --- a/packages/webui/src/styles/base.css +++ b/packages/webui/src/styles/base.css @@ -1,3 +1,4 @@ +@import url('https://fonts.googleapis.com/css?family=Roboto:400,600,700|Roboto Mono:400,600,700&display=swap'); @import './allotment.css'; @tailwind base; @@ -9,71 +10,18 @@ html, body, main#root { @apply h-full; - @apply bg-gray-200 text-gray-800; -} - -/** - * Text colors - */ -.text-primary { - @apply text-gray-800 dark:text-gray-800; -} - -.text-secondary { - @apply text-gray-500 dark:text-gray-500; -} - -.text-neutral { - @apply text-gray-400 dark:text-gray-400; -} - -/** - * Background colors - */ -.bg-primary { - @apply bg-gray-200; -} - -.bg-secondary { - @apply bg-gray-300; -} - -.bg-neutral { - @apply bg-white; -} - -/** - * Border colors - */ -.border-primary { - @apply border-gray-400; -} - -.border-secondary { - @apply border-gray-300; -} - -.border-neutral { - @apply border-gray-200; -} - -.ring-primary { - @apply ring-gray-400; + @apply bg-manatee-200 text-manatee-950; } /** * Override native elements */ -hr { - @apply block border-0 border-b border-b-gray-400/50 h-0 my-1; -} - ::-webkit-scrollbar { - @apply bg-gray-300 w-2 h-3; + @apply bg-transparent w-2 h-3; } ::-webkit-scrollbar-thumb { - @apply bg-gray-400; + @apply bg-manatee-700; } input[type='search']::-webkit-search-decoration, @@ -101,8 +49,8 @@ input[type='search']::-webkit-search-results-decoration { .p-cell { @apply py-1 px-2; - @apply first:pl-4; - @apply last:pr-4; + @apply first:pl-3; + @apply last:pr-3; } .absolute-v-center { @@ -123,7 +71,7 @@ input[type='search']::-webkit-search-results-decoration { */ .code { @apply !font-mono !tracking-normal !break-all; - @apply text-slate-900; + @apply text-manatee-950; } .code-inline { @@ -135,7 +83,7 @@ input[type='search']::-webkit-search-results-decoration { @apply code; @apply w-full py-4; @apply block overflow-auto; - @apply bg-gray-200; + @apply bg-manatee-100; @apply rounded; } @@ -145,3 +93,26 @@ input[type='search']::-webkit-search-results-decoration { @apply hover:bg-opacity-50 hover:bg-white; @apply hover:cursor-default; } + +/** + * Tables + */ +.badge-200 { + @apply bg-[#318B47] text-white +} + +.badge-300 { + @apply bg-yellow-400 text-white +} + +.badge-400 { + @apply bg-[#AC0535] text-white +} + +.badge-500 { + @apply bg-[#613C8D] text-white +} + +.badge-abort { + @apply bg-manatee-700 text-white +} diff --git a/packages/webui/src/systems/GraphQL.tsx b/packages/webui/src/systems/GraphQL.tsx index b734112..b5a258a 100644 --- a/packages/webui/src/systems/GraphQL.tsx +++ b/packages/webui/src/systems/GraphQL.tsx @@ -44,7 +44,7 @@ export default class GraphQLSystem implements System { const { operationType, operationName, query } = data; return ( - <> +
{operationName} @@ -56,7 +56,7 @@ export default class GraphQLSystem implements System { {query} - +
); } } diff --git a/packages/webui/src/systems/Sanity.tsx b/packages/webui/src/systems/Sanity.tsx index 03ae8bb..1b8c134 100644 --- a/packages/webui/src/systems/Sanity.tsx +++ b/packages/webui/src/systems/Sanity.tsx @@ -48,7 +48,7 @@ export default class SanitySystem implements System { const { type, query } = data; return ( - <> +
{type} @@ -57,7 +57,7 @@ export default class SanitySystem implements System { {query} - +
); } diff --git a/packages/webui/src/systems/index.tsx b/packages/webui/src/systems/index.tsx index b63a44a..376fc4f 100644 --- a/packages/webui/src/systems/index.tsx +++ b/packages/webui/src/systems/index.tsx @@ -60,20 +60,10 @@ export function ListDataComponent({ trace }: SystemDetailProps): React.ReactNode export function RequestDetailsComponent({ trace }: SystemDetailProps): React.ReactNode { const Component = callOrFallback(trace, 'getRequestDetailComponent'); - return Component ? ( - <> -
- {Component} - - ) : null; + return Component ? Component : null; } export function ResponseDetailsComponent({ trace }: SystemDetailProps): React.ReactNode { const Component = callOrFallback(trace, 'getResponseDetailComponent'); - return Component ? ( - <> -
- {Component} - - ) : null; + return Component ? Component : null; } diff --git a/packages/webui/src/utils/styles.test.ts b/packages/webui/src/utils/styles.test.ts new file mode 100644 index 0000000..e3b85ce --- /dev/null +++ b/packages/webui/src/utils/styles.test.ts @@ -0,0 +1,35 @@ +import { HttpRequestState } from '@envyjs/core'; + +import { badgeStyle } from './styles'; + +describe('badgeStyle', () => { + const scenarios = [ + { statusCode: 500, bgColor: 'badge-500' }, + { statusCode: 404, bgColor: 'badge-400' }, + { statusCode: 300, bgColor: 'badge-300' }, + { statusCode: 200, bgColor: 'badge-200' }, + ]; + + it.each(scenarios)('should have $bgColor for HTTP $statusCode responses', ({ statusCode, bgColor }) => { + const trace = { + http: { + state: HttpRequestState.Received, + statusCode, + }, + } as any; + + const style = badgeStyle(trace); + expect(style).toEqual(bgColor); + }); + + it('should have badge-abort for aborted HTTP requests', () => { + const trace = { + http: { + state: HttpRequestState.Aborted, + }, + } as any; + + const style = badgeStyle(trace); + expect(style).toEqual('badge-abort'); + }); +}); diff --git a/packages/webui/src/utils/styles.ts b/packages/webui/src/utils/styles.ts new file mode 100644 index 0000000..d85de4f --- /dev/null +++ b/packages/webui/src/utils/styles.ts @@ -0,0 +1,16 @@ +import { HttpRequestState } from '@envyjs/core'; + +import { Trace } from '@/types'; + +export function badgeStyle(trace: Trace) { + const { statusCode, state } = trace.http || {}; + + if (state === HttpRequestState.Aborted) return 'badge-abort'; + + if (statusCode) { + if (statusCode >= 500) return 'badge-500'; + else if (statusCode >= 400) return 'badge-400'; + else if (statusCode >= 300) return 'badge-300'; + else if (statusCode >= 200) return 'badge-200'; + } +} diff --git a/packages/webui/tailwind.config.js b/packages/webui/tailwind.config.js index f27be55..2750995 100644 --- a/packages/webui/tailwind.config.js +++ b/packages/webui/tailwind.config.js @@ -1,4 +1,5 @@ /** @type {import('tailwindcss').Config} */ +const defaultTheme = require('tailwindcss/defaultTheme'); export default { darkMode: ['class'], content: ['./src/index.html', './src/**/*.tsx'], @@ -45,8 +46,12 @@ export default { 950: '#0F1212', }, }, + fontFamily: { + sans: ['Roboto', ...defaultTheme.fontFamily.sans], + mono: ['Roboto Mono', ...defaultTheme.fontFamily.mono], + }, fontSize: { - '2xs': ['0.6rem', '0.9rem'], + '2xs': ['0.625rem', '0.875rem'], }, }, },