diff --git a/Makefile b/Makefile index 15b1964..4fa0385 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ ldflags= \ -X "github.com/mike76-dev/hostscore/internal/build.NodeBinaryName=hsd" \ -X "github.com/mike76-dev/hostscore/internal/build.NodeVersion=0.1.1" \ -X "github.com/mike76-dev/hostscore/internal/build.ClientBinaryName=hsc" \ --X "github.com/mike76-dev/hostscore/internal/build.ClientVersion=0.1.0" \ +-X "github.com/mike76-dev/hostscore/internal/build.ClientVersion=0.2.0" \ -X "github.com/mike76-dev/hostscore/internal/build.GitRevision=${GIT_DIRTY}${GIT_REVISION}" \ -X "github.com/mike76-dev/hostscore/internal/build.BuildTime=${BUILD_TIME}" diff --git a/cmd/hsc/api.go b/cmd/hsc/api.go index f4d6c72..85d54c7 100644 --- a/cmd/hsc/api.go +++ b/cmd/hsc/api.go @@ -40,6 +40,11 @@ type hostsResponse struct { Total int `json:"total"` } +type onlineHostsResponse struct { + APIResponse + OnlineHosts int `json:"onlineHosts"` +} + type scansResponse struct { APIResponse PublicKey types.PublicKey `json:"publicKey"` @@ -184,6 +189,9 @@ func (api *portalAPI) buildHTTPRoutes() { router.GET("/hosts", func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { api.hostsHandler(w, req, ps) }) + router.GET("/hosts/online", func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { + api.onlineHostsHandler(w, req, ps) + }) router.GET("/scans", func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { api.scansHandler(w, req, ps) }) @@ -522,6 +530,37 @@ func (api *portalAPI) statusHandler(w http.ResponseWriter, _ *http.Request, _ ht }) } +func (api *portalAPI) onlineHostsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { + network := strings.ToLower(req.FormValue("network")) + if network == "" { + writeJSON(w, APIResponse{ + Status: "error", + Message: "network not provided", + }) + return + } + var count int + api.mu.RLock() + if network == "mainnet" { + for _, host := range api.hosts { + if api.isOnline(*host) { + count++ + } + } + } else if network == "zen" { + for _, host := range api.hostsZen { + if api.isOnline(*host) { + count++ + } + } + } + api.mu.RUnlock() + writeJSON(w, onlineHostsResponse{ + APIResponse: APIResponse{Status: "ok"}, + OnlineHosts: count, + }) +} + func writeJSON(w http.ResponseWriter, obj interface{}) { w.Header().Set("Content-Type", "application/json; charset=utf-8") err := json.NewEncoder(w).Encode(obj) diff --git a/web/src/api/index.ts b/web/src/api/index.ts index 91c55ca..63f40a2 100644 --- a/web/src/api/index.ts +++ b/web/src/api/index.ts @@ -42,12 +42,21 @@ export const getHost = async ( .catch(error => console.log(error)) } -export const getStatus = async (): Promise<{ status: string, message: string, version: string, nodes: NodeStatus[] }> => { +export const getStatus = async (): + Promise<{ status: string, message: string, version: string, nodes: NodeStatus[] }> => { const url = '/status' return instance.get(url) .then(response => response.data) .catch(error => console.log(error)) } +export const getOnlineHosts = async (network: string): + Promise<{ status: string, message: string, onlineHosts: number }> => { + const url = '/hosts/online?network=' + network + return instance.get(url) + .then(response => response.data) + .catch(error => console.log(error)) +} + export * from './types' export * from './helpers' \ No newline at end of file diff --git a/web/src/components/Header/Header.css b/web/src/components/Header/Header.css index 3f07f3c..a645d4e 100644 --- a/web/src/components/Header/Header.css +++ b/web/src/components/Header/Header.css @@ -23,4 +23,10 @@ .header-dark-mode select:focus { border: 1px solid transparent; outline: 2px solid var(--outlineDark); +} + +@media screen and (max-width: 1000px) { + .header-container { + padding-bottom: 0.75rem; + } } \ No newline at end of file diff --git a/web/src/components/NetworkSelector/NetworkSelector.css b/web/src/components/NetworkSelector/NetworkSelector.css index 6622f25..aabadb9 100644 --- a/web/src/components/NetworkSelector/NetworkSelector.css +++ b/web/src/components/NetworkSelector/NetworkSelector.css @@ -1,5 +1,6 @@ .network-selector-container { margin: 0 1rem; + position: relative; } .network-selector-select { @@ -12,6 +13,14 @@ outline: 2px solid var(--outlineLight); } +.network-selector-text { + position: absolute; + right: 0; + font-size: 0.8rem; + margin-top: 0.5rem; + white-space: nowrap; +} + @media screen and (max-width: 1000px) { .network-selector-container { margin: 0 0.5rem; diff --git a/web/src/components/NetworkSelector/NetworkSelector.tsx b/web/src/components/NetworkSelector/NetworkSelector.tsx index 543a837..dbdef00 100644 --- a/web/src/components/NetworkSelector/NetworkSelector.tsx +++ b/web/src/components/NetworkSelector/NetworkSelector.tsx @@ -1,7 +1,7 @@ import './NetworkSelector.css' import React, { useState, useEffect } from 'react' import { useLocation, useNavigate } from 'react-router-dom' -import { useExcludedPaths } from '../../api' +import { useExcludedPaths, getOnlineHosts } from '../../api' type NetworkSelectorProps = { network: string, @@ -13,6 +13,7 @@ export const NetworkSelector = (props: NetworkSelectorProps) => { const navigate = useNavigate() const [network, switchNetwork] = useState(props.network) const excludedPaths = useExcludedPaths() + const [onlineHosts, setOnlineHosts] = useState(0) useEffect(() => { if (excludedPaths.includes(location.pathname)) return if (location.pathname.indexOf('/zen') === 0) { @@ -22,6 +23,13 @@ export const NetworkSelector = (props: NetworkSelectorProps) => { useEffect(() => { switchNetwork(props.network) }, [props.network]) + useEffect(() => { + if (network === '') return + getOnlineHosts(network) + .then(data => { + if (data && data.status === 'ok') setOnlineHosts(data.onlineHosts) + }) + }, [network]) return (