Skip to content
Draft
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
710 changes: 295 additions & 415 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
"it-first": "^1.0.6",
"it-last": "^1.0.5",
"it-map": "^1.0.5",
"kubo-rpc-client": "^5.4.0",
"kubo-rpc-client": "file:../js-kubo-rpc-client",
"milliseconds": "^1.0.3",
"money-clip": "^3.0.5",
"multiformats": "^13.4.0",
Expand Down
14 changes: 13 additions & 1 deletion public/locales/en/diagnostics.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"title": "Diagnostics",
"tabs": {
"logs": "Logs",
"retrieval-check": "Retrieval Check"
"retrieval-check": "Retrieval Check",
"dht-provide": "DHT Provide"
},
"logs": {
"title": "Node Logs",
Expand Down Expand Up @@ -93,5 +94,16 @@
"upgradeDescription": "To use the log level management features, you need to upgrade to a newer version of Kubo that supports getting log levels and tailing logs from the RPC API endpoint.",
"downloadKubo": "Download Kubo"
}
},
"dhtProvide": {
"title": "DHT Provide",
"node": "Node",
"unknown": "Unknown",
"cidLabel": "CID to provide",
"provideButton": "Trigger Provide",
"clear": "Clear",
"provideTriggered": "Provide triggered for {cid}",
"noCid": "Please enter a CID",
"description": "Trigger a background provide operation for a CID. This will announce the CID on the DHT so other peers can discover providers for it."
}
}
90 changes: 90 additions & 0 deletions src/components/card/card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React from 'react'
import { Box } from '../box/Box'

/**
* Card root
*/
export const Card: React.FC<{
className?: string
style?: React.CSSProperties
children: React.ReactNode
}> = ({ className = '', style, children }) => {
return (
<Box
className={`ba b--black-10 br2 bg-white ${className}`}
style={{ padding: 0, ...style }}
>
{children}
</Box>
)
}

/**
* Card header
*/
export const CardHeader: React.FC<{
className?: string
children: React.ReactNode
}> = ({ className = '', children }) => {
return (
<div className={`pa3 bb b--black-10 ${className}`}>
{children}
</div>
)
}

/**
* Card title
*/
export const CardTitle: React.FC<{
className?: string
children: React.ReactNode
}> = ({ className = '', children }) => {
return (
<h3 className={`f6 fw6 ma0 ${className}`}>
{children}
</h3>
)
}

/**
* Card description
*/
export const CardDescription: React.FC<{
className?: string
children: React.ReactNode
}> = ({ className = '', children }) => {
return (
<div className={`f7 charcoal-muted mt1 ${className}`}>
{children}
</div>
)
}

/**
* Card content
*/
export const CardContent: React.FC<{
className?: string
children: React.ReactNode
}> = ({ className = '', children }) => {
return (
<div className={`pa3 ${className}`}>
{children}
</div>
)
}

/**
* Card footer
*/
export const CardFooter: React.FC<{
className?: string
children: React.ReactNode
}> = ({ className = '', children }) => {
return (
<div className={`pa3 bt b--black-10 ${className}`}>
{children}
</div>
)
}
32 changes: 32 additions & 0 deletions src/components/metric-row/MetricRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react'
import Tooltip from '../tooltip/Tooltip'

export const MetricRow: React.FC<{
label: string
value: React.ReactNode
highlight?: boolean
className?: string
tooltip?: string
}> = ({ label, value, highlight = false, className = '', tooltip }) => {
const labelNode = (
<span className='f7 charcoal-muted'>{label}</span>
)

return (
<div className={`flex justify-between items-center ${className}`}>
{tooltip && (
<Tooltip text={tooltip}>
{labelNode}
</Tooltip>
)}

{!tooltip && labelNode}

<span className={`f6 ${highlight ? 'fw6 green' : ''}`}>
{value}
</span>
</div>
)
}

export default MetricRow
9 changes: 9 additions & 0 deletions src/contexts/ProvideStat/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { KuboRPCClient } from 'kubo-rpc-client'
import type { ProvideStatOptions, ProvideStats } from './types'

export async function getProvideStats (
ipfs: KuboRPCClient,
options: ProvideStatOptions = { all: true }
): Promise<ProvideStats> {
return ipfs.provide.stat(options)
}
5 changes: 5 additions & 0 deletions src/contexts/ProvideStat/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Export the context provider and hook
export { ProvideProvider, useProvide } from './provide-context'

export * from './types'
export * from './api'
109 changes: 109 additions & 0 deletions src/contexts/ProvideStat/provide-context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import React, {
createContext,
useContext,
useCallback,
useEffect,
useMemo,
useState
} from 'react'
import { useBridgeSelector } from '../../helpers/context-bridge'
import { useAgentVersionMinimum } from '../../lib/hooks/use-agent-version-minimum'
import type { KuboRPCClient } from 'kubo-rpc-client'
import type { ProvideStats, ProvideStatOptions } from './types'
import { getProvideStats } from './api'

export interface ProvideContextValue {
data: ProvideStats | null
loading: boolean
error: Error | null
lastUpdated: number | null
refresh: (options?: ProvideStatOptions) => Promise<void>
autoRefreshEnabled: boolean
setAutoRefreshEnabled: (enabled: boolean) => void
isAgentVersionSupported: boolean
}

const ProvideContext = createContext<ProvideContextValue | undefined>(undefined)
ProvideContext.displayName = 'ProvideContext'

export const ProvideProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const ipfs = useBridgeSelector('selectIpfs') as KuboRPCClient
const ipfsConnected = useBridgeSelector('selectIpfsConnected') as boolean

const { ok: isAgentVersionSupported } = useAgentVersionMinimum({
minimumVersion: '0.39.0',
requiredAgent: 'kubo'
})

const [data, setData] = useState<ProvideStats | null>(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState<Error | null>(null)
const [lastUpdated, setLastUpdated] = useState<number | null>(null)
const [autoRefreshEnabled, _setAutoRefreshEnabled] = useState<boolean>(() => {
try {
const raw = localStorage.getItem('provide.autoRefresh')
return raw === null ? true : raw === 'true'
} catch {
return true
}
})

const refresh = useCallback(async (options: ProvideStatOptions = { all: true }) => {
if (!ipfs || !ipfsConnected || !isAgentVersionSupported) return

setLoading(true)
try {
const stats = await getProvideStats(ipfs, options)
setData(stats)
setError(null)
setLastUpdated(Date.now())
} catch (err) {
setError(err as Error)
} finally {
setLoading(false)
}
}, [ipfs, ipfsConnected, isAgentVersionSupported])

// Auto-refresh every 60s
useEffect(() => {
// Run an initial refresh and then schedule periodic refreshes only
// when autoRefreshEnabled is true. The toggle is persisted in localStorage.
refresh()
if (!autoRefreshEnabled) return

const id = setInterval(refresh, 60_000)
return () => clearInterval(id)
}, [refresh, autoRefreshEnabled])

const setAutoRefreshEnabled = useCallback((enabled: boolean) => {
try {
localStorage.setItem('provide.autoRefresh', String(enabled))
} catch (_) {}
_setAutoRefreshEnabled(enabled)
}, [])

const value = useMemo<ProvideContextValue>(() => ({
data,
loading,
error,
lastUpdated,
refresh,
autoRefreshEnabled,
setAutoRefreshEnabled,
isAgentVersionSupported
}), [data, loading, error, lastUpdated, refresh, autoRefreshEnabled, isAgentVersionSupported, setAutoRefreshEnabled])

return (
<ProvideContext.Provider value={value}>
{children}
</ProvideContext.Provider>
)
}

export function useProvide (): ProvideContextValue {
const ctx = useContext(ProvideContext)
if (!ctx) {
throw new Error('useProvide must be used within a ProvideProvider')
}
return ctx
}
103 changes: 103 additions & 0 deletions src/contexts/ProvideStat/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import type { HTTPRPCOptions } from 'kubo-rpc-client'
export interface LegacyProvideStats {
total_reprovides: number
avg_reprovide_duration: string
last_reprovide_duration: string
last_run?: string
reprovide_interval?: string
}

export interface ProvideConnectivity {
status: 'online' | 'disconnected'
since: string
}

export interface ProvideQueues {
pending_key_provides: number
pending_region_provides: number
pending_region_reprovides: number
}

export interface ProvideSchedule {
keys: number
regions: number
avg_prefix_length: number
next_reprovide_at: string
next_reprovide_prefix: string
}

export interface ProvideWorkers {
max: number
active: number
active_periodic: number
active_burst: number
dedicated_periodic: number
dedicated_burst: number
queued_periodic: number
queued_burst: number
max_provide_conns_per_worker: number
}

export interface ProvideTiming {
uptime: number
reprovides_interval: number
cycle_start: string
current_time_offset: number
max_reprovide_delay: number
}

export interface ProvideOperations {
ongoing: {
key_provides: number
region_provides: number
key_reprovides: number
region_reprovides: number
}
past: {
keys_provided: number
records_provided: number
keys_failed: number
keys_provided_per_minute?: number
keys_reprovided_per_minute?: number
region_reprovide_duration?: number
avg_keys_per_reprovide?: number
regions_reprovided_last_cycle?: number
}
}

export interface ProvideNetwork {
peers: number
reachable: number
complete_keyspace_coverage: boolean
avg_region_size: number
avg_holders: number
replication_factor: number
}
export interface SweepProvideStats {
closed: boolean
connectivity: ProvideConnectivity
queues: ProvideQueues
schedule: ProvideSchedule
workers: ProvideWorkers
timing: ProvideTiming
operations: ProvideOperations
network: ProvideNetwork
}
export interface ProvideStatOptions extends HTTPRPCOptions {
all?: boolean
lan?: boolean
compact?: boolean
connectivity?: boolean
network?: boolean
queues?: boolean
schedule?: boolean
timing?: boolean
workers?: boolean
operations?: boolean
}

export interface ProvideStats {
Sweep?: SweepProvideStats
Legacy?: LegacyProvideStats | null
FullRT?: boolean
}
Loading