Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions public/locales/en/diagnostics.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"description": "View detailed diagnostic information about your IPFS node and system configuration.",
"tabs": {
"logs": "Logs",
"check": "IPFS Check",
"connectivity": "Connectivity"
},
"logs": {
Expand Down
12 changes: 11 additions & 1 deletion src/bundles/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { composeBundles, createCacheBundle } from 'redux-bundler'
import { composeBundles, createCacheBundle, createSelector } from 'redux-bundler'
import ipfsProvider from './ipfs-provider.js'
import appIdle from './app-idle.js'
import nodeBandwidthChartBundle from './node-bandwidth-chart.js'
Expand All @@ -23,8 +23,18 @@ import experimentsBundle from './experiments.js'
import cliTutorModeBundle from './cli-tutor-mode.js'
import gatewayBundle from './gateway.js'
import ipnsBundle from './ipns.js'
import { contextBridge } from '../helpers/context-bridge'

export default composeBundles(
{
name: 'bridgedContextCatchAll',
reactRouteInfoToBridge: createSelector(
'selectRouteInfo',
(routeInfo) => {
contextBridge.setContext('selectRouteInfo', routeInfo)
}
)
},
Comment on lines +29 to +37
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

for selectors created by redux-bundler itself (i.e. createRouteBundle) i figured this would be a good place to hook into those

createCacheBundle({
cacheFn: bundleCache.set
}),
Expand Down
33 changes: 33 additions & 0 deletions src/bundles/routes-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type React from 'react'

/**
* src/bundles/routes.js creates a RouteBundle from redux-bundler that provides some objects that are not typed.
*/

/**
* The type for the object provided by `selectRouteInfo` selector.
*
* These types are not 100% accurate and only filled out as accurately as possible as needed.
*/
export interface RouteInfo {
/**
* The value of the currently matched pattern from src/bundles/routes.js
*/
page: React.ReactNode

params: {
// if you are on #/diagnostics/logs, this will be equal to '/logs'
path: string
}

/**
* This will match whatever key is set in src/bundles/routes.js for the page that is currently active.
* For the diagnostics page, this will be equal to '/diagnostics*'
*/
pattern: string

/**
* The hash of the url, without the hash symbol.
*/
url: string
}
2 changes: 1 addition & 1 deletion src/bundles/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default createRouteBundle({
'/settings*': SettingsPage,
'/welcome': WelcomePage,
'/blank': BlankPage,
'/diagnostics': DiagnosticsPage,
'/diagnostics*': DiagnosticsPage,
'/status*': StatusPage,
'/': StatusPage,
'': StatusPage
Expand Down
48 changes: 48 additions & 0 deletions src/diagnostics/check-screen/check-screen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, { useRef, useEffect } from 'react'
import { useDebouncedCallback } from '../../lib/hooks/use-debounced-callback'

const CheckScreen: React.FC = () => {
const ref = useRef<HTMLIFrameElement>(null)

const requestSize = useDebouncedCallback(() => {
const iframe = ref.current
if (!iframe) return
ref.current?.contentWindow?.postMessage({ type: 'iframe-size:request' }, '*')
}, 100)

useEffect(() => {
const iframe = ref.current
if (!iframe) return

const onMsg = (e: MessageEvent<any>) => {
if (e.data?.type !== 'iframe-size:report') return
iframe.style.height = `${e.data.height}px`
}
const onLoad = () => requestSize()

window.addEventListener('message', onMsg)
window.addEventListener('resize', requestSize)
iframe.addEventListener('load', onLoad)

// intial size request since the iframe ref is available.
requestSize()

return () => {
window.removeEventListener('message', onMsg)
window.removeEventListener('resize', requestSize)
iframe.removeEventListener('load', onLoad)
}
}, [requestSize])

return (
<iframe
ref={ref}
className="db bn w-100 overflow-y-hidden overflow-x-hidden"
title="Check Screen"
// src="https://check.ipfs.network/" // TODO: uncomment when https://github.com/ipfs/ipfs-check/pull/102 is deployed to check.ipfs.network
Comment thread
lidel marked this conversation as resolved.
Outdated
src="http://127.0.0.1:3001/"
/>
)
}

export default CheckScreen
66 changes: 47 additions & 19 deletions src/diagnostics/diagnostics-content.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,69 @@
import React, { useState } from 'react'
import React, { useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import LogsScreen from './logs-screen/logs-screen.js'
import { LogsProvider } from '../contexts/logs/index'
import { IdentityProvider } from '../contexts/identity-context'
import CheckScreen from './check-screen/check-screen.js'
import { useBridgeSelector } from '../helpers/context-bridge'
import { RouteInfo } from 'src/bundles/routes-types'
Comment thread
SgtPooki marked this conversation as resolved.
Outdated

interface DiagnosticsContentProps {
}

type TabKey = 'logs'
type TabKey = 'logs' | 'check'

function getTabKeyFromUrl (path: string): TabKey {
if (path === '/check') {
return 'check'
}
return 'logs'
}

interface TabButtonProps {
tabKey: TabKey
label: string
active: boolean
}

const TabButton = ({ tabKey, label, active }: TabButtonProps) => (
<a
key={tabKey}
href={`#/diagnostics${tabKey === 'logs' ? '' : `/${tabKey}`}`}
className={`pv2 mr2 bg-transparent bn pointer fw6 no-underline ${
active ? 'charcoal bb bw2 b--blue' : 'charcoal-muted underline-hover'
}`}
>
{label}
</a>
)

const DiagnosticsContent: React.FC<DiagnosticsContentProps> = () => {
const { t } = useTranslation('diagnostics')
const [activeTab, setActiveTab] = useState<TabKey>('logs')

const renderTabButton = (tabKey: TabKey, label: string) => (
<button
key={tabKey}
className={`pv2 mr2 bg-transparent bn pointer fw6 ${
activeTab === tabKey
? 'charcoal bb bw2 b--blue'
: 'charcoal-muted hover-charcoal'
}`}
onClick={() => setActiveTab(tabKey)}
>
{label}
</button>
)
const routeInfo = useBridgeSelector<RouteInfo>('selectRouteInfo')
const activeTab = getTabKeyFromUrl(routeInfo?.params.path ?? '')

const isMounted = useRef(false)
useEffect(() => {
isMounted.current = true
return () => {
isMounted.current = false
}
}, [])

const renderTabContent = () => {
switch (activeTab) {
case 'logs':
return (
<IdentityProvider>
<LogsProvider>
<LogsScreen />
<LogsScreen />
</LogsProvider>
</IdentityProvider>
)
case 'check':
return (
<CheckScreen />
)
default:
return null
}
Expand All @@ -47,7 +74,8 @@ const DiagnosticsContent: React.FC<DiagnosticsContentProps> = () => {
{/* Tab Navigation */}
<div className='bb b--black-20 mb4'>
<nav className='flex'>
{renderTabButton('logs', t('tabs.logs'))}
<TabButton tabKey='logs' label={t('tabs.logs')} active={activeTab === 'logs'} />
<TabButton tabKey='check' label={t('tabs.check')} active={activeTab === 'check'} />
</nav>
</div>

Expand Down
Loading