diff --git a/CHANGELOG.md b/CHANGELOG.md index f4cad57d567879ade2e3a39f779b1d0a2f3d3c40..79333fa5fbf35b83d8b0521136ac14c8ab391d90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## [0.25.0] - 2020-02-07 + +### Added + +- [RemoteData Package](https://package.elm-lang.org/packages/krisajenkins/remotedata/latest/) +- Message when Data is yet to be Fetched +- Spinner when Loading Data + +## Changed + +- Fetched Data is now represented with RemoteData Datatype + +## [0.24.0] - 2020-01-28 + +### Added + +- Support for differents Node Status + +## [0.23.0] - 2020-01-27 + +### Added + +- Link to Selected Stake Pool on Cardano Explorer + ## [0.22.1] - 2020-01-26 ### Changed @@ -101,11 +125,11 @@ ### Added -- [DateFormat Library](https://package.elm-lang.org/packages/ryannhg/date-format/) +- [DateFormat Package](https://package.elm-lang.org/packages/ryannhg/date-format/latest/) - Current Time to compare with schedules - Countdown to next block - Stake Pools Leaders sorted by `created_at` -- [Elm ISO8601 Date Strings Library](https://package.elm-lang.org/packages/rtfeldman/elm-iso8601-date-strings/latest/) +- [Elm ISO8601 Date Strings Package](https://package.elm-lang.org/packages/rtfeldman/elm-iso8601-date-strings/latest/) ### Changed diff --git a/elm.json b/elm.json index 326b6738c8eda07f2f4b853ff82cd98117363aff..23ae342193c5699819ec57b14843d08382212f75 100644 --- a/elm.json +++ b/elm.json @@ -16,6 +16,7 @@ "elm/svg": "1.0.1", "elm/time": "1.0.0", "elm/url": "1.0.0", + "krisajenkins/remotedata": "6.0.1", "mdgriffith/elm-style-animation": "4.0.0", "mdgriffith/elm-ui": "1.1.5", "rtfeldman/elm-iso8601-date-strings": "1.1.3", diff --git a/index.html b/index.html index bbd3e15b81b61313a52a99bfd331a5bdf0a9225f..a9aea68838b8c9e9b6e38026f3971b66e94f75d5 100644 --- a/index.html +++ b/index.html @@ -12,6 +12,11 @@ + diff --git a/src/OxHead.elm b/src/OxHead.elm index f6522e92ddee3faf477c1bc2dde412c562f106a1..8aa2e24212a517c23f7f88aa824ede0ebf68844f 100644 --- a/src/OxHead.elm +++ b/src/OxHead.elm @@ -4,7 +4,7 @@ import Browser import Browser.Events import Browser.Navigation as Nav import DateFormat.Relative as RelativeDateFormat -import Element exposing (Device, DeviceClass(..), Element, Orientation(..), alignBottom, alignLeft, alignRight, alignTop, centerX, centerY, classifyDevice, clipX, column, el, fill, fillPortion, height, image, link, maximum, minimum, newTabLink, onLeft, onRight, padding, paddingEach, paddingXY, px, rgb255, row, scrollbarY, spacing, text, width, wrappedRow) +import Element exposing (Device, DeviceClass(..), Element, Orientation(..), alignBottom, alignLeft, alignRight, alignTop, centerX, centerY, classifyDevice, column, el, fill, fillPortion, height, image, link, maximum, minimum, newTabLink, onLeft, onRight, padding, paddingEach, paddingXY, px, rgb255, row, scrollbarY, spacing, text, width, wrappedRow) import Element.Background as Background import Element.Border as Border import Element.Events as Events @@ -17,6 +17,7 @@ import Http exposing (get) import Iso8601 import Json.Decode exposing (index, int, list, map2, string) import Json.Decode.Pipeline exposing (required) +import RemoteData exposing (RemoteData(..), WebData) import Svg exposing (circle, defs, g, polygon, polyline, rect, svg) import Svg.Attributes as SvgA import Task @@ -69,34 +70,34 @@ type alias Model = , status : Status , nodeUrl : String , isMonitoring : Bool - , nodeStats : Maybe NodeStats + , nodeStats : WebData NodeStats , showNodeStats : Bool - , networkStatsList : Maybe (List NetworkStats) + , networkStatsList : WebData (List NetworkStats) , showNetworkStats : Bool - , nodeSettings : Maybe NodeSettings + , nodeSettings : WebData NodeSettings , showNodeSettings : Bool - , utxoList : Maybe (List UTXO) + , utxoList : WebData (List UTXO) , showUTXOList : Bool - , tip : Maybe String + , tip : WebData String , showTip : Bool - , stakePoolsList : Maybe (List String) + , stakePoolsList : WebData (List String) , showStakePoolsList : Bool - , stakeDistribution : Maybe StakeDistribution + , stakeDistribution : WebData StakeDistribution , showStakeDistribution : Bool - , stakePoolDetail : Maybe StakePool + , stakePoolDetail : WebData StakePool , stakePoolID : String , myStakePoolID : String , showZeroStaked : Bool , sortAscending : Bool - , leaders : Maybe (List Int) + , leaders : WebData (List Int) , showLeaders : Bool - , leadersLogs : Maybe (List LeadersLogs) + , leadersLogs : WebData (List LeadersLogs) , showLeadersLogsList : Bool - , fragmentLogs : Maybe (List FragmentLogs) + , fragmentLogs : WebData (List FragmentLogs) , showFragmentLogsList : Bool - , block : Maybe String - , nextBlock : Maybe String - , account : Maybe Account + , block : WebData String + , nextBlock : WebData String + , account : WebData Account , blockId : String , accountId : String , nextBlockCount : Int @@ -118,31 +119,31 @@ initialModel url key = , status = Loaded , nodeUrl = "http://127.0.0.1:3100" , isMonitoring = False - , nodeStats = Nothing + , nodeStats = NotAsked , showNodeStats = True , showNetworkStats = False - , networkStatsList = Nothing - , nodeSettings = Nothing + , networkStatsList = NotAsked + , nodeSettings = NotAsked , showNodeSettings = True - , utxoList = Nothing - , tip = Nothing + , utxoList = NotAsked + , tip = NotAsked , showUTXOList = True - , stakePoolsList = Nothing - , stakeDistribution = Nothing - , stakePoolDetail = Nothing + , stakePoolsList = NotAsked + , stakeDistribution = NotAsked + , stakePoolDetail = NotAsked , stakePoolID = "" , myStakePoolID = "" , showTip = True - , leaders = Nothing - , leadersLogs = Nothing + , leaders = NotAsked + , leadersLogs = NotAsked , showStakePoolsList = True - , fragmentLogs = Nothing - , block = Nothing + , fragmentLogs = NotAsked + , block = NotAsked , showStakeDistribution = True , showZeroStaked = False , sortAscending = False - , nextBlock = Nothing - , account = Nothing + , nextBlock = NotAsked + , account = NotAsked , showLeaders = True , blockId = "" , accountId = "" @@ -207,22 +208,22 @@ type Msg = LinkClicked Browser.UrlRequest | UrlChanged Url.Url | FetchDataClicked - | GotNodeStatsMessage (Result Http.Error NodeStats) - | GotNetworkStatsListMessage (Result Http.Error (List NetworkStats)) - | GotNodeSettingsMessage (Result Http.Error NodeSettings) - | GotUTXOListMessage (Result Http.Error (List UTXO)) - | GotTipMessage (Result Http.Error String) - | GotStakePoolsListMessage (Result Http.Error (List String)) - | GotStakePoolMessage (Result Http.Error StakePool) - | GotStakeDistributionMessage (Result Http.Error StakeDistribution) - | GotLeadersMessage (Result Http.Error (List Int)) - | GotLeadersLogsListMessage (Result Http.Error (List LeadersLogs)) - | GotFragmentLogsListMessage (Result Http.Error (List FragmentLogs)) - | GotBlockMessage (Result Http.Error String) - | GotNextBlockMessage (Result Http.Error String) - | GotAccountMessage (Result Http.Error Account) - | GotShutdownMessage (Result Http.Error String) - | GotDeleteLeaderMessage (Result Http.Error String) + | GotNodeStatsMessage (WebData NodeStats) + | GotNetworkStatsListMessage (WebData (List NetworkStats)) + | GotNodeSettingsMessage (WebData NodeSettings) + | GotUTXOListMessage (WebData (List UTXO)) + | GotTipMessage (WebData String) + | GotStakePoolsListMessage (WebData (List String)) + | GotStakePoolMessage (WebData StakePool) + | GotStakeDistributionMessage (WebData StakeDistribution) + | GotLeadersMessage (WebData (List Int)) + | GotLeadersLogsListMessage (WebData (List LeadersLogs)) + | GotFragmentLogsListMessage (WebData (List FragmentLogs)) + | GotBlockMessage (WebData String) + | GotNextBlockMessage (WebData String) + | GotAccountMessage (WebData Account) + | GotShutdownMessage (WebData String) + | GotDeleteLeaderMessage (WebData String) | ToggleMonitoring | ToggleNodeStatsPanel | ToggleNodeSettingsPanel @@ -262,7 +263,7 @@ type Msg type Status - = Loading + = Fetching | Loaded | Errored String @@ -294,7 +295,7 @@ getNetworkStatsList : Model -> Cmd Msg getNetworkStatsList model = Http.get { url = model.nodeUrl ++ "/api/v0/network/stats" - , expect = expectJson GotNetworkStatsListMessage (list decodeNetworkStats) + , expect = expectJson (RemoteData.fromResult >> GotNetworkStatsListMessage) (list decodeNetworkStats) } @@ -302,7 +303,18 @@ getNetworkStatsList model = -- NodeStats -type alias NodeStats = +type NodeStats + = Initializing BootstrappingNodeStats + | Running RunningNodeStats + + +type alias BootstrappingNodeStats = + { version : String + , state : String + } + + +type alias RunningNodeStats = { version : String , state : String , blockRecvCnt : Int @@ -319,11 +331,18 @@ type alias NodeStats = } -decodeNodeStats : Json.Decode.Decoder NodeStats -decodeNodeStats = - Json.Decode.succeed NodeStats +decodeBootstrappingNodeStats : Json.Decode.Decoder BootstrappingNodeStats +decodeBootstrappingNodeStats = + Json.Decode.succeed BootstrappingNodeStats |> Json.Decode.Pipeline.required "version" Json.Decode.string - |> Json.Decode.Pipeline.optional "state" Json.Decode.string "null" + |> Json.Decode.Pipeline.required "state" Json.Decode.string + + +decodeRunningNodeStats : Json.Decode.Decoder RunningNodeStats +decodeRunningNodeStats = + Json.Decode.succeed RunningNodeStats + |> Json.Decode.Pipeline.required "version" Json.Decode.string + |> Json.Decode.Pipeline.required "state" Json.Decode.string |> Json.Decode.Pipeline.required "blockRecvCnt" Json.Decode.int |> Json.Decode.Pipeline.required "lastBlockContentSize" Json.Decode.int |> Json.Decode.Pipeline.required "lastBlockDate" Json.Decode.string @@ -341,7 +360,7 @@ getNodeStats : Model -> Cmd Msg getNodeStats model = Http.get { url = model.nodeUrl ++ "/api/v0/node/stats" - , expect = expectJson GotNodeStatsMessage decodeNodeStats + , expect = expectJson (RemoteData.fromResult >> GotNodeStatsMessage) (Json.Decode.oneOf [ Json.Decode.map Running decodeRunningNodeStats, Json.Decode.map Initializing decodeBootstrappingNodeStats ]) } @@ -438,7 +457,7 @@ getNodeSettings : Model -> Cmd Msg getNodeSettings model = Http.get { url = model.nodeUrl ++ "/api/v0/settings" - , expect = expectJson GotNodeSettingsMessage decodeNodeSettings + , expect = expectJson (RemoteData.fromResult >> GotNodeSettingsMessage) decodeNodeSettings } @@ -467,7 +486,7 @@ getUTXOList : Model -> Cmd Msg getUTXOList model = Http.get { url = model.nodeUrl ++ "/api/v0/utxo" - , expect = expectJson GotUTXOListMessage (list decodeUTXO) + , expect = expectJson (RemoteData.fromResult >> GotUTXOListMessage) (list decodeUTXO) } @@ -479,7 +498,7 @@ getTip : Model -> Cmd Msg getTip model = Http.get { url = model.nodeUrl ++ "/api/v0/tip" - , expect = Http.expectString GotTipMessage + , expect = Http.expectString (RemoteData.fromResult >> GotTipMessage) } @@ -550,7 +569,7 @@ getStakePool : Model -> String -> Cmd Msg getStakePool model stakePoolID = Http.get { url = model.nodeUrl ++ "/api/v0/stake_pool/" ++ stakePoolID - , expect = Http.expectJson GotStakePoolMessage decodeStakePool + , expect = Http.expectJson (RemoteData.fromResult >> GotStakePoolMessage) decodeStakePool } @@ -596,7 +615,7 @@ getStakeDistribution : Model -> Cmd Msg getStakeDistribution model = Http.get { url = model.nodeUrl ++ "/api/v0/stake" - , expect = expectJson GotStakeDistributionMessage decodeStakeDistribution + , expect = expectJson (RemoteData.fromResult >> GotStakeDistributionMessage) decodeStakeDistribution } @@ -608,7 +627,7 @@ getLeaders : Model -> Cmd Msg getLeaders model = Http.get { url = model.nodeUrl ++ "/api/v0/leaders" - , expect = Http.expectJson GotLeadersMessage (Json.Decode.list Json.Decode.int) + , expect = Http.expectJson (RemoteData.fromResult >> GotLeadersMessage) (Json.Decode.list Json.Decode.int) } @@ -695,7 +714,7 @@ getLeadersLogsList : Model -> Cmd Msg getLeadersLogsList model = Http.get { url = model.nodeUrl ++ "/api/v0/leaders/logs" - , expect = expectJson GotLeadersLogsListMessage (list decodeLeadersLogs) + , expect = expectJson (RemoteData.fromResult >> GotLeadersLogsListMessage) (list decodeLeadersLogs) } @@ -726,7 +745,7 @@ getFragmentLogsList : Model -> Cmd Msg getFragmentLogsList model = Http.get { url = model.nodeUrl ++ "/api/v0/fragment/logs" - , expect = expectJson GotFragmentLogsListMessage (list decodeFragmentLogs) + , expect = expectJson (RemoteData.fromResult >> GotFragmentLogsListMessage) (list decodeFragmentLogs) } @@ -738,7 +757,7 @@ getBlock : Model -> Cmd Msg getBlock model = Http.get { url = model.nodeUrl ++ "/api/v0/block/" ++ model.blockId - , expect = Http.expectString GotBlockMessage + , expect = Http.expectString (RemoteData.fromResult >> GotBlockMessage) } @@ -760,7 +779,7 @@ getNextBlock model = else "" ) - , expect = Http.expectString GotNextBlockMessage + , expect = Http.expectString (RemoteData.fromResult >> GotNextBlockMessage) } @@ -798,7 +817,7 @@ getAccount : Model -> Cmd Msg getAccount model = Http.get { url = model.nodeUrl ++ "/api/v0/account/" ++ model.accountId - , expect = Http.expectJson GotAccountMessage decodeAccount + , expect = Http.expectJson (RemoteData.fromResult >> GotAccountMessage) decodeAccount } @@ -813,7 +832,7 @@ deleteLeader model = , headers = [] , url = model.nodeUrl ++ "/api/v0/leaders/" ++ model.leaderId , body = Http.emptyBody - , expect = Http.expectString GotDeleteLeaderMessage + , expect = Http.expectString (RemoteData.fromResult >> GotDeleteLeaderMessage) , timeout = Nothing , tracker = Nothing } @@ -827,7 +846,7 @@ getShutdown : Model -> Cmd Msg getShutdown model = Http.get { url = model.nodeUrl ++ "/api/v0/shutdown" - , expect = Http.expectString GotShutdownMessage + , expect = Http.expectString (RemoteData.fromResult >> GotShutdownMessage) } @@ -852,11 +871,11 @@ update msg model = FetchDataClicked -> case toRoute (Url.toString model.url) of Node -> - ( { model | status = Loading }, Cmd.batch <| List.map (\x -> x model) [ getNodeStats, getNetworkStatsList, getNodeSettings ] ) + ( { model | status = Fetching, nodeStats = Loading, networkStatsList = Loading, nodeSettings = Loading }, Cmd.batch <| List.map (\x -> x model) [ getNodeStats, getNetworkStatsList, getNodeSettings ] ) Distribution -> - ( { model | status = Loading } - , if model.stakePoolDetail == Nothing && not (String.isEmpty model.stakePoolID) then + ( { model | status = Fetching, stakeDistribution = Loading } + , if model.stakePoolDetail == NotAsked && not (String.isEmpty model.stakePoolID) then Cmd.batch [ getStakeDistribution model, getStakePool model model.stakePoolID ] else @@ -864,7 +883,7 @@ update msg model = ) Leaders -> - ( { model | status = Loading }, getLeadersLogsList model ) + ( { model | status = Fetching, leadersLogs = Loading }, getLeadersLogsList model ) _ -> ( model, Cmd.none ) @@ -900,11 +919,11 @@ update msg model = Monitor _ -> case toRoute (Url.toString model.url) of Node -> - ( { model | status = Loading }, Cmd.batch <| List.map (\x -> x model) [ getNodeStats, getNetworkStatsList, getNodeSettings ] ) + ( { model | status = Fetching }, Cmd.batch <| List.map (\x -> x model) [ getNodeStats, getNetworkStatsList, getNodeSettings ] ) Distribution -> - ( { model | status = Loading } - , if model.stakePoolDetail == Nothing && not (String.isEmpty model.stakePoolID) then + ( { model | status = Fetching } + , if model.stakePoolDetail == NotAsked && not (String.isEmpty model.stakePoolID) then Cmd.batch [ getStakeDistribution model, getStakePool model model.stakePoolID ] else @@ -912,7 +931,7 @@ update msg model = ) Leaders -> - ( { model | status = Loading }, getLeadersLogsList model ) + ( { model | status = Fetching }, getLeadersLogsList model ) _ -> ( model, Cmd.none ) @@ -953,326 +972,54 @@ update msg model = ToggleFragmentLogsPanel -> ( { model | showFragmentLogsList = not model.showFragmentLogsList }, Cmd.none ) - GotNodeStatsMessage (Ok response) -> - ( { model | status = Loaded, nodeStats = Just response }, Cmd.none ) - - GotNodeStatsMessage (Err error) -> - case error of - Http.BadUrl url -> - ( { model | status = Errored ("Couldn't retrieve Node Status: " ++ url) }, Cmd.none ) - - Http.Timeout -> - ( { model | status = Errored ("Couldn't retrieve Node Status: " ++ "Timeout") }, Cmd.none ) - - Http.NetworkError -> - ( { model | status = Errored ("Couldn't retrieve Node Status: " ++ "Network") }, Cmd.none ) - - Http.BadStatus statusCode -> - ( { model | status = Errored ("Couldn't retrieve Node Status: " ++ String.fromInt statusCode) }, Cmd.none ) - - Http.BadBody err -> - ( { model | status = Errored ("Couldn't retrieve Node Status: " ++ err) }, Cmd.none ) - - GotNetworkStatsListMessage (Ok response) -> - ( { model | status = Loaded, networkStatsList = Just response }, Cmd.none ) - - GotNetworkStatsListMessage (Err error) -> - case error of - Http.BadUrl url -> - ( { model | status = Errored ("Couldn't retrieve Network Status: " ++ url) }, Cmd.none ) - - Http.Timeout -> - ( { model | status = Errored ("Couldn't retrieve Network Status: " ++ "Timeout") }, Cmd.none ) - - Http.NetworkError -> - ( { model | status = Errored ("Couldn't retrieve Network Status: " ++ "Network") }, Cmd.none ) - - Http.BadStatus statusCode -> - ( { model | status = Errored ("Couldn't retrieve Network Status: " ++ String.fromInt statusCode) }, Cmd.none ) - - Http.BadBody err -> - ( { model | status = Errored ("Couldn't retrieve Network Status: " ++ err) }, Cmd.none ) - - GotNodeSettingsMessage (Ok response) -> - ( { model | status = Loaded, nodeSettings = Just response }, Cmd.none ) - - GotNodeSettingsMessage (Err error) -> - case error of - Http.BadUrl url -> - ( { model | status = Errored ("Couldn't retrieve Node Settings: " ++ url) }, Cmd.none ) - - Http.Timeout -> - ( { model | status = Errored ("Couldn't retrieve Node Settings: " ++ "Timeout") }, Cmd.none ) - - Http.NetworkError -> - ( { model | status = Errored ("Couldn't retrieve Node Settings: " ++ "Network") }, Cmd.none ) - - Http.BadStatus statusCode -> - ( { model | status = Errored ("Couldn't retrieve Node Settings: " ++ String.fromInt statusCode) }, Cmd.none ) - - Http.BadBody err -> - ( { model | status = Errored ("Couldn't retrieve Node Settings: " ++ err), error = err }, Cmd.none ) - - GotUTXOListMessage (Ok response) -> - ( { model | status = Loaded, utxoList = Just response }, Cmd.none ) - - GotUTXOListMessage (Err error) -> - case error of - Http.BadUrl url -> - ( { model | status = Errored ("Couldn't retrieve UTXO List: " ++ url) }, Cmd.none ) - - Http.Timeout -> - ( { model | status = Errored ("Couldn't retrieve UTXO List: " ++ "Timeout") }, Cmd.none ) - - Http.NetworkError -> - ( { model | status = Errored ("Couldn't retrieve UTXO List: " ++ "Network") }, Cmd.none ) - - Http.BadStatus statusCode -> - ( { model | status = Errored ("Couldn't retrieve UTXO List: " ++ String.fromInt statusCode) }, Cmd.none ) - - Http.BadBody err -> - ( { model | status = Errored ("Couldn't retrieve UTXO List: " ++ err) }, Cmd.none ) - - GotTipMessage (Ok response) -> - ( { model | status = Loaded, tip = Just response }, Cmd.none ) - - GotTipMessage (Err error) -> - case error of - Http.BadUrl url -> - ( { model | status = Errored ("Couldn't retrieve Stake Pools: " ++ url) }, Cmd.none ) - - Http.Timeout -> - ( { model | status = Errored ("Couldn't retrieve Stake Pools: " ++ "Timeout") }, Cmd.none ) - - Http.NetworkError -> - ( { model | status = Errored ("Couldn't retrieve Stake Pools: " ++ "Network") }, Cmd.none ) - - Http.BadStatus statusCode -> - ( { model | status = Errored ("Couldn't retrieve Stake Pools: " ++ String.fromInt statusCode) }, Cmd.none ) - - Http.BadBody err -> - ( { model | status = Errored ("Couldn't retrieve Stake Pools: " ++ err) }, Cmd.none ) - - GotStakePoolsListMessage (Ok response) -> - ( { model | status = Loaded, stakePoolsList = Just response }, Cmd.none ) - - GotStakePoolsListMessage (Err error) -> - case error of - Http.BadUrl url -> - ( { model | status = Errored ("Couldn't retrieve Stake Pools: " ++ url) }, Cmd.none ) - - Http.Timeout -> - ( { model | status = Errored ("Couldn't retrieve Stake Pools: " ++ "Timeout") }, Cmd.none ) + GotNodeStatsMessage response -> + ( { model | status = Loaded, nodeStats = response }, Cmd.none ) - Http.NetworkError -> - ( { model | status = Errored ("Couldn't retrieve Stake Pools: " ++ "Network") }, Cmd.none ) + GotNetworkStatsListMessage response -> + ( { model | status = Loaded, networkStatsList = response }, Cmd.none ) - Http.BadStatus statusCode -> - ( { model | status = Errored ("Couldn't retrieve Stake Pools: " ++ String.fromInt statusCode) }, Cmd.none ) + GotNodeSettingsMessage response -> + ( { model | status = Loaded, nodeSettings = response }, Cmd.none ) - Http.BadBody err -> - ( { model | status = Errored ("Couldn't retrieve Stake Pools: " ++ err) }, Cmd.none ) + GotUTXOListMessage response -> + ( { model | status = Loaded, utxoList = response }, Cmd.none ) - GotStakeDistributionMessage (Ok response) -> - ( { model | status = Loaded, stakeDistribution = Just response }, Cmd.none ) + GotTipMessage response -> + ( { model | status = Loaded, tip = response }, Cmd.none ) - GotStakeDistributionMessage (Err error) -> - case error of - Http.BadUrl url -> - ( { model | status = Errored ("Couldn't retrieve Stake Distribution: " ++ url), error = url }, Cmd.none ) + GotStakePoolsListMessage response -> + ( { model | status = Loaded, stakePoolsList = response }, Cmd.none ) - Http.Timeout -> - ( { model | status = Errored ("Couldn't retrieve Stake Distribution: " ++ "Timeout"), error = "Timeout" }, Cmd.none ) + GotStakeDistributionMessage response -> + ( { model | status = Loaded, stakeDistribution = response }, Cmd.none ) - Http.NetworkError -> - ( { model | status = Errored ("Couldn't retrieve Stake Distribution: " ++ "Network"), error = "Network" }, Cmd.none ) + GotStakePoolMessage response -> + ( { model | status = Loaded, stakePoolDetail = response }, Cmd.none ) - Http.BadStatus statusCode -> - ( { model | status = Errored ("Couldn't retrieve Stake Distribution: " ++ String.fromInt statusCode), error = String.fromInt statusCode }, Cmd.none ) + GotLeadersMessage response -> + ( { model | status = Loaded, leaders = response }, Cmd.none ) - Http.BadBody err -> - ( { model | status = Errored ("Couldn't retrieve Stake Distribution: " ++ err), error = err }, Cmd.none ) + GotLeadersLogsListMessage response -> + ( { model | status = Loaded, leadersLogs = response }, Cmd.none ) - GotStakePoolMessage (Ok response) -> - ( { model | status = Loaded, stakePoolDetail = Just response }, Cmd.none ) + GotFragmentLogsListMessage response -> + ( { model | status = Loaded, fragmentLogs = response }, Cmd.none ) - GotStakePoolMessage (Err error) -> - case error of - Http.BadUrl url -> - ( { model | status = Errored ("Couldn't retrieve Stake Pool: " ++ url), error = url }, Cmd.none ) + GotBlockMessage response -> + ( { model | status = Loaded, block = response }, Cmd.none ) - Http.Timeout -> - ( { model | status = Errored ("Couldn't retrieve Stake Pool: " ++ "Timeout"), error = "Timeout" }, Cmd.none ) + GotNextBlockMessage response -> + ( { model | status = Loaded, nextBlock = response }, Cmd.none ) - Http.NetworkError -> - ( { model | status = Errored ("Couldn't retrieve Stake Pool: " ++ "Network"), error = "Network" }, Cmd.none ) + GotAccountMessage response -> + ( { model | status = Loaded, account = response }, Cmd.none ) - Http.BadStatus statusCode -> - ( { model | status = Errored ("Couldn't retrieve Stake Pool: " ++ String.fromInt statusCode), error = String.fromInt statusCode }, Cmd.none ) - - Http.BadBody err -> - ( { model | status = Errored ("Couldn't retrieve Stake Pool: " ++ err), error = err }, Cmd.none ) - - GotLeadersMessage (Ok response) -> - ( { model | status = Loaded, leaders = Just response }, Cmd.none ) - - GotLeadersMessage (Err error) -> - case error of - Http.BadUrl url -> - ( { model | status = Errored ("Couldn't retrieve Leaders: " ++ url) }, Cmd.none ) - - Http.Timeout -> - ( { model | status = Errored ("Couldn't retrieve Leaders: " ++ "Timeout") }, Cmd.none ) - - Http.NetworkError -> - ( { model | status = Errored ("Couldn't retrieve Leaders: " ++ "Network") }, Cmd.none ) - - Http.BadStatus statusCode -> - ( { model | status = Errored ("Couldn't retrieve Leaders: " ++ String.fromInt statusCode) }, Cmd.none ) - - Http.BadBody err -> - ( { model | status = Errored ("Couldn't retrieve Leaders: " ++ err) }, Cmd.none ) - - GotLeadersLogsListMessage (Ok response) -> - ( { model | status = Loaded, leadersLogs = Just response }, Cmd.none ) - - GotLeadersLogsListMessage (Err error) -> - case error of - Http.BadUrl url -> - ( { model | status = Errored ("Couldn't retrieve Leaders Logs List: " ++ url) }, Cmd.none ) - - Http.Timeout -> - ( { model | status = Errored ("Couldn't retrieve Leaders Logs List: " ++ "Timeout") }, Cmd.none ) - - Http.NetworkError -> - ( { model | status = Errored ("Couldn't retrieve Leaders Logs List: " ++ "Network") }, Cmd.none ) - - Http.BadStatus statusCode -> - ( { model | status = Errored ("Couldn't retrieve Leaders Logs List: " ++ String.fromInt statusCode) }, Cmd.none ) - - Http.BadBody err -> - ( { model | status = Errored ("Couldn't retrieve Leaders Logs List: " ++ err) }, Cmd.none ) - - GotFragmentLogsListMessage (Ok response) -> - ( { model | status = Loaded, fragmentLogs = Just response }, Cmd.none ) - - GotFragmentLogsListMessage (Err error) -> - case error of - Http.BadUrl url -> - ( { model | status = Errored ("Couldn't retrieve Fragment Logs List: " ++ url) }, Cmd.none ) - - Http.Timeout -> - ( { model | status = Errored ("Couldn't retrieve Fragment Logs List: " ++ "Timeout") }, Cmd.none ) - - Http.NetworkError -> - ( { model | status = Errored ("Couldn't retrieve Fragment Logs List: " ++ "Network") }, Cmd.none ) - - Http.BadStatus statusCode -> - ( { model | status = Errored ("Couldn't retrieve Fragment Logs List: " ++ String.fromInt statusCode) }, Cmd.none ) - - Http.BadBody err -> - ( { model | status = Errored ("Couldn't retrieve Fragment Logs List: " ++ err) }, Cmd.none ) - - GotBlockMessage (Ok response) -> - ( { model | status = Loaded, block = Just response }, Cmd.none ) - - GotBlockMessage (Err error) -> - case error of - Http.BadUrl url -> - ( { model | status = Errored ("Couldn't retrieve Block: " ++ url) }, Cmd.none ) - - Http.Timeout -> - ( { model | status = Errored ("Couldn't retrieve Block: " ++ "Timeout") }, Cmd.none ) - - Http.NetworkError -> - ( { model | status = Errored ("Couldn't retrieve Block: " ++ "Network") }, Cmd.none ) - - Http.BadStatus statusCode -> - ( { model | status = Errored ("Couldn't retrieve Block: " ++ String.fromInt statusCode) }, Cmd.none ) - - Http.BadBody err -> - ( { model | status = Errored ("Couldn't retrieve Block: " ++ err) }, Cmd.none ) - - GotNextBlockMessage (Ok response) -> - ( { model | status = Loaded, nextBlock = Just response }, Cmd.none ) - - GotNextBlockMessage (Err error) -> - case error of - Http.BadUrl url -> - ( { model | status = Errored ("Couldn't retrieve Next Block: " ++ url) }, Cmd.none ) - - Http.Timeout -> - ( { model | status = Errored ("Couldn't retrieve Next Block: " ++ "Timeout") }, Cmd.none ) - - Http.NetworkError -> - ( { model | status = Errored ("Couldn't retrieve Next Block: " ++ "Network") }, Cmd.none ) - - Http.BadStatus statusCode -> - ( { model | status = Errored ("Couldn't retrieve Next Block: " ++ String.fromInt statusCode) }, Cmd.none ) - - Http.BadBody err -> - ( { model | status = Errored ("Couldn't retrieve Next Block: " ++ err) }, Cmd.none ) - - GotAccountMessage (Ok response) -> - ( { model | status = Loaded, account = Just response }, Cmd.none ) - - GotAccountMessage (Err error) -> - case error of - Http.BadUrl url -> - ( { model | status = Errored ("Couldn't retrieve Account: " ++ url) }, Cmd.none ) - - Http.Timeout -> - ( { model | status = Errored ("Couldn't retrieve Account: " ++ "Timeout") }, Cmd.none ) - - Http.NetworkError -> - ( { model | status = Errored ("Couldn't retrieve Account: " ++ "Network") }, Cmd.none ) - - Http.BadStatus statusCode -> - ( { model | status = Errored ("Couldn't retrieve Account: " ++ String.fromInt statusCode) }, Cmd.none ) - - Http.BadBody err -> - ( { model | status = Errored ("Couldn't retrieve Account: " ++ err) }, Cmd.none ) - - GotDeleteLeaderMessage (Ok _) -> + GotDeleteLeaderMessage _ -> ( { model | status = Loaded }, Cmd.none ) - GotDeleteLeaderMessage (Err error) -> - case error of - Http.BadUrl url -> - ( { model | status = Errored ("Couldn't Delete Leader: " ++ url) }, Cmd.none ) - - Http.Timeout -> - ( { model | status = Errored ("Couldn't Delete Leader: " ++ "Timeout") }, Cmd.none ) - - Http.NetworkError -> - ( { model | status = Errored ("Couldn't Delete Leader: " ++ "Network") }, Cmd.none ) - - Http.BadStatus statusCode -> - ( { model | status = Errored ("Couldn't Delete Leader: " ++ String.fromInt statusCode) }, Cmd.none ) - - Http.BadBody err -> - ( { model | status = Errored ("Couldn't Delete Leader: " ++ err) }, Cmd.none ) - - GotShutdownMessage (Ok _) -> + GotShutdownMessage _ -> ( { model | status = Loaded }, Cmd.none ) - GotShutdownMessage (Err error) -> - case error of - Http.BadUrl url -> - ( { model | status = Errored ("Couldn't Shutdown Node: " ++ url) }, Cmd.none ) - - Http.Timeout -> - ( { model | status = Errored ("Couldn't Shutdown Node: " ++ "Timeout") }, Cmd.none ) - - Http.NetworkError -> - ( { model | status = Errored ("Couldn't Shutdown Node: " ++ "Network") }, Cmd.none ) - - Http.BadStatus statusCode -> - ( { model | status = Errored ("Couldn't Shutdown Node: " ++ String.fromInt statusCode) }, Cmd.none ) - - Http.BadBody err -> - ( { model | status = Errored ("Couldn't Shutdown Node: " ++ err) }, Cmd.none ) - StakePoolIdClicked id -> ( { model | stakePoolID = id }, getStakePool model id ) @@ -1281,7 +1028,7 @@ update msg model = ( { model | stakePoolID = id }, getStakePool model id ) else - ( { model | stakePoolID = id, stakePoolDetail = Nothing }, Cmd.none ) + ( { model | stakePoolID = id, stakePoolDetail = NotAsked }, Cmd.none ) NodeURLChanged url -> let @@ -1424,7 +1171,7 @@ view model = row [ height fill, width fill ] [ viewSidebar model , column [ width fill, height fill, scrollbarY ] - [ column [ width fill, height fill, paddingXY 10 0, spacing 10 ] + [ column [ width fill, height fill, spacing 10 ] [ -- , row [] -- [ Input.text [ centerY, width (px 300) ] { onChange = NodeURLChanged, text = model.nodeUrl, placeholder = Maybe.Just (Input.placeholder [ centerY ] (text "Insert Node URL")), label = Input.labelLeft [ centerY ] (text "Node URL: ") } -- ] @@ -1473,18 +1220,17 @@ view model = viewHome Node -> - viewNode model + if RemoteData.isSuccess model.nodeStats && RemoteData.isFailure model.nodeSettings && RemoteData.isFailure model.networkStatsList then + webDataView (viewNodeStats model.showNodeStats model.zone) model.nodeStats + + else + webDataView (viewNode model) (merge3 model.nodeStats model.nodeSettings model.networkStatsList) Distribution -> viewDistribution model Leaders -> - case model.leadersLogs of - Just leadersLogsList -> - viewLeadersLogs model.time leadersLogsList model.zone - - Nothing -> - Element.none + webDataView (viewLeadersLogs model.time model.zone) model.leadersLogs Settings -> viewSettings model @@ -1516,15 +1262,19 @@ viewSidebar model = ] , column [ centerX, spacing 40 ] [ el [ centerX, Font.color (Element.rgb255 94 96 102), Element.mouseOver [ Font.color (Element.rgb 225 225 225) ] ] (Input.button [] { onPress = Just FetchDataClicked, label = withTooltip "Fetch Data" <| fetchDataSvg }) - , Input.button [ centerX, Font.color (Element.rgb255 94 96 102), Element.mouseOver [ Font.color (Element.rgb 225 225 225) ] ] - { onPress = Just ToggleMonitoring - , label = - if model.isMonitoring then - withTooltip "Stop Monitoring" <| monitorSvg + , if model.isMonitoring then + Input.button [ centerX, Font.color (Element.rgb 225 225 225), Element.mouseOver [ Font.color (Element.rgb 225 225 225) ] ] + { onPress = Just ToggleMonitoring + , label = + withTooltip "Stop Monitoring" <| el [ Element.htmlAttribute <| Html.Attributes.style "-webkit-animation" "1s spinner infinite" ] <| monitorSvg + } - else + else + Input.button [ centerX, Font.color (Element.rgb255 94 96 102), Element.mouseOver [ Font.color (Element.rgb 225 225 225) ] ] + { onPress = Just ToggleMonitoring + , label = withTooltip "Start Monitoring" <| monitorSvg - } + } , el [ alignBottom , centerX @@ -1587,20 +1337,49 @@ viewSidebar model = , paddingXY 10 0 , centerX ] - [ el [ centerX ] <| - case model.status of - Loading -> - loadingSvg + [ case model.status of + Fetching -> + el [ centerX, Element.htmlAttribute <| Html.Attributes.style "-webkit-animation" "1s spinner infinite" ] <| fetchingSvg - Loaded -> - loadedSvg + Loaded -> + el [ centerX ] <| loadedSvg - Errored _ -> - erroredSvg + Errored _ -> + el [ centerX ] <| erroredSvg ] ] +webDataView : (a -> Element Msg) -> WebData a -> Element Msg +webDataView successView webData = + case webData of + NotAsked -> + el [ centerX, centerY, fontMono ] <| text "Nothing to see here, fetch some data!" + + Loading -> + el [ centerX, centerY, Element.htmlAttribute <| Html.Attributes.style "-webkit-animation" "1s spinner infinite" ] <| loadingSvg + + Failure err -> + case err of + Http.BadUrl url -> + text <| "Couldn't retrieve: " ++ url + + Http.Timeout -> + text <| "Couldn't retrieve: " ++ "Timeout" + + Http.NetworkError -> + text <| "Couldn't retrieve: " ++ "Network" + + Http.BadStatus statusCode -> + text <| "Couldn't retrieve: " ++ String.fromInt statusCode + + Http.BadBody error -> + text <| "Couldn't retrieve: " ++ error + + Success data -> + successView data + + viewHome : Element Msg viewHome = column [ padding 20, spacing 20 ] @@ -1652,8 +1431,19 @@ viewHome = ] -viewNode : Model -> Element Msg -viewNode model = +merge3 : + RemoteData e a + -> RemoteData e b + -> RemoteData e c + -> RemoteData e ( a, b, c ) +merge3 a b c = + RemoteData.map (\x y z -> ( x, y, z )) a + |> RemoteData.andMap b + |> RemoteData.andMap c + + +viewNode : Model -> ( NodeStats, NodeSettings, List NetworkStats ) -> Element Msg +viewNode model ( nodeStats, nodeSettings, networkStats ) = column [ Font.size 14 , fontMono @@ -1663,138 +1453,126 @@ viewNode model = ] [ wrappedRow [ width fill, spacing 10 ] - [ case model.nodeStats of - Just stats -> - column - [ Border.color (Element.rgb255 198 205 214) - , Border.width 2 - , padding 10 - , spacing 5 - , alignTop - , width fill + [ viewNodeStats model.showNodeStats model.zone nodeStats + , viewNodeSettings model.showNodeSettings model.zone nodeSettings + ] + , viewNetworkStats model.showNetworkStats model.zone networkStats + ] + + + +-- , case model.utxoList of +-- Just stats -> +-- viewPanel (viewUTXOList stats) +-- Nothing -> +-- Element.none +-- , case model.tip of +-- Just tip -> +-- el [] <| text tip +-- Nothing -> +-- Element.none +-- , case model.stakePoolsList of +-- Just stakePoolsList -> +-- viewPanel (viewStakePools stakePoolsList) +-- Nothing -> +-- Element.none +-- , case model.stakeDistribution of +-- Just stakeDistribution -> +-- viewPanel (viewStakeDistribution stakeDistribution) +-- Nothing -> +-- Element.none +-- , case model.leaders of +-- Just leadersList -> +-- viewPanel (viewLeaders leadersList) +-- Nothing -> +-- Element.none +-- , case model.leadersLogs of +-- Just leadersLogsList -> +-- viewPanel (viewLeadersLogs leadersLogsList) +-- Nothing -> +-- Element.none +-- , case model.fragmentLogs of +-- Just fragmentLogsList -> +-- viewPanel (viewFragmentLogs fragmentLogsList) +-- Nothing -> +-- Element.none +-- , case model.account of +-- Just acc -> +-- viewPanel (viewAccount acc) +-- Nothing -> +-- Element.none + + +viewNodeStats : Bool -> Time.Zone -> NodeStats -> Element Msg +viewNodeStats showNodeStats zone nodeStats = + case nodeStats of + Initializing stats -> + column + [ padding 10 + , spacing 5 + , alignTop + , width fill + , fontMono + , Background.color (Element.rgb255 255 214 68) + ] + [ row [ width fill, Border.widthEach { bottom = 1, left = 0, right = 0, top = 0 }, paddingXY 2 10 ] + [ el [ alignLeft, fontNormal, Font.size 24 ] <| text <| "Node Status" + ] + , column + [ width fill, spacing 10, padding 10 ] + [ wrappedRow [ width fill ] + [ el [ alignLeft ] (withTooltip "Node app version" <| text <| "Version") + , el [ alignRight ] <| text <| stats.version ] - (viewNodeStats stats model.showNodeStats model.zone) - - Nothing -> - Element.none - , case model.nodeSettings of - Just stats -> - column - [ Border.color (Element.rgb255 198 205 214) - , Border.width 2 - , padding 10 - , spacing 5 - , alignTop - , width fill + , wrappedRow [ width fill ] + [ el [ alignLeft ] (withTooltip "State of the node" <| text <| "State") + , el [ alignRight ] <| text <| stats.state ] - (viewNodeSettings stats model.showNodeSettings model.zone) - - Nothing -> - Element.none - - -- , case model.utxoList of - -- Just stats -> - -- viewPanel (viewUTXOList stats) - -- Nothing -> - -- Element.none - -- , case model.tip of - -- Just tip -> - -- el [] <| text tip - -- Nothing -> - -- Element.none - -- , case model.stakePoolsList of - -- Just stakePoolsList -> - -- viewPanel (viewStakePools stakePoolsList) - -- Nothing -> - -- Element.none - -- , case model.stakeDistribution of - -- Just stakeDistribution -> - -- viewPanel (viewStakeDistribution stakeDistribution) - -- Nothing -> - -- Element.none - -- , case model.leaders of - -- Just leadersList -> - -- viewPanel (viewLeaders leadersList) - -- Nothing -> - -- Element.none - -- , case model.leadersLogs of - -- Just leadersLogsList -> - -- viewPanel (viewLeadersLogs leadersLogsList) - -- Nothing -> - -- Element.none - -- , case model.fragmentLogs of - -- Just fragmentLogsList -> - -- viewPanel (viewFragmentLogs fragmentLogsList) - -- Nothing -> - -- Element.none - -- , case model.account of - -- Just acc -> - -- viewPanel (viewAccount acc) - -- Nothing -> - -- Element.none - ] - , case model.networkStatsList of - Just networkStatsData -> - column - [ Border.color (Element.rgb255 198 205 214) - , Border.width 2 - , padding 10 - , spacing 5 - , alignTop - , width fill ] - (viewNetworkStats networkStatsData model.showNetworkStats model.zone) + ] - Nothing -> - Element.none - ] + Running stats -> + column + [ Border.color (Element.rgb255 198 205 214) + , Border.width 2 + , padding 10 + , spacing 5 + , alignTop + , width fill + ] + (viewRunningNodeStats stats showNodeStats zone) viewDistribution : Model -> Element Msg viewDistribution model = - row - [ Font.size 14 - , fontMono - , spacing 15 - , padding 10 - , width fill - ] - -- [ case model.stakePoolsList of - -- Just stakePoolsList -> - -- viewPanel (viewStakePools stakePoolsList model.showStakePoolsList) - -- Nothing -> - -- Element.none - [ case model.stakeDistribution of - Just stakeDistribution -> - column [ centerX, width fill ] - (viewStakeDistribution stakeDistribution - model.stakePoolDetail - model.stakePoolID - model.myStakePoolID - model.showZeroStaked - model.sortAscending - model.showStakePoolInputSuggestions - ) + webDataView + (viewStakeDistribution + model.stakePoolDetail + model.stakePoolID + model.myStakePoolID + model.showZeroStaked + model.sortAscending + model.showStakePoolInputSuggestions + ) + model.stakeDistribution - Nothing -> - Element.none - -- , case model.leaders of - -- Just leadersList -> - -- viewPanel (viewLeaders leadersList) - -- Nothing -> - -- Element.none - -- , case model.leadersLogs of - -- Just leadersLogsList -> - -- viewPanel (viewLeadersLogs leadersLogsList) - -- Nothing -> - -- Element.none - -- , case model.fragmentLogs of - -- Just fragmentLogsList -> - -- viewPanel (viewFragmentLogs fragmentLogsList) - -- Nothing -> - -- Element.none - ] + +-- , case model.leaders of +-- Just leadersList -> +-- viewPanel (viewLeaders leadersList) +-- Nothing -> +-- Element.none +-- , case model.leadersLogs of +-- Just leadersLogsList -> +-- viewPanel (viewLeadersLogs leadersLogsList) +-- Nothing -> +-- Element.none +-- , case model.fragmentLogs of +-- Just fragmentLogsList -> +-- viewPanel (viewFragmentLogs fragmentLogsList) +-- Nothing -> +-- Element.none viewSettings : Model -> Element Msg @@ -1924,8 +1702,8 @@ viewPanel viewContent = viewContent -viewNodeStats : NodeStats -> Bool -> Time.Zone -> List (Element Msg) -viewNodeStats nodeStats showNodeStats timeZone = +viewRunningNodeStats : RunningNodeStats -> Bool -> Time.Zone -> List (Element Msg) +viewRunningNodeStats nodeStats showNodeStats timeZone = [ row [ width fill, Border.widthEach { bottom = 1, left = 0, right = 0, top = 0 }, paddingXY 2 10 ] [ el [ alignLeft, fontNormal, Font.size 24 ] <| text <| "Node Status" , Input.button [ alignRight ] { onPress = Just ToggleNodeStatsPanel, label = toggleIcon showNodeStats } @@ -1991,116 +1769,132 @@ viewNodeStats nodeStats showNodeStats timeZone = ] -viewNetworkStats : List NetworkStats -> Bool -> Time.Zone -> List (Element Msg) -viewNetworkStats networkStats showNetworkStats timeZone = - [ column [ width fill, spacing 10 ] - [ row [ Border.widthEach { bottom = 1, left = 0, right = 0, top = 0 }, width fill, paddingXY 2 10 ] - [ el [ alignLeft, fontNormal, Font.size 24 ] <| text <| "Network Status" - , Input.button toggleButtonStyle { onPress = Just ToggleNetworkStatsPanel, label = toggleIcon showNetworkStats } - ] - , row [ fontNormal, Font.size 16, width fill, Font.bold ] - [ el [ alignLeft ] <| text <| "Connections" - , el [ alignRight ] <| text <| String.fromInt (List.length networkStats) - ] +viewNetworkStats : Bool -> Time.Zone -> List NetworkStats -> Element Msg +viewNetworkStats showNetworkStats timeZone networkStats = + column + [ Border.color (Element.rgb255 198 205 214) + , Border.width 2 + , padding 10 + , spacing 5 + , alignTop + , width fill ] - , if showNetworkStats then - column - [ width fill - , spacing 10 - , padding 10 - , scrollbarY - , height (fill |> maximum 400) + [ column [ width fill, spacing 10 ] + [ row [ Border.widthEach { bottom = 1, left = 0, right = 0, top = 0 }, width fill, paddingXY 2 10 ] + [ el [ alignLeft, fontNormal, Font.size 24 ] <| text <| "Network Status" + , Input.button toggleButtonStyle { onPress = Just ToggleNetworkStatsPanel, label = toggleIcon showNetworkStats } + ] + , row [ fontNormal, Font.size 16, width fill, Font.bold ] + [ el [ alignLeft ] <| text <| "Connections" + , el [ alignRight ] <| text <| String.fromInt (List.length networkStats) + ] ] - (List.map - (\stats -> - column [ width fill, padding 20, Background.color (Element.rgb255 240 241 242), Border.solid, Border.color (Element.rgb255 198 205 214), Border.widthEach { bottom = 0, left = 2, right = 0, top = 0 } ] - [ wrappedRow [ width fill ] - [ withTooltip "Hex-encoded node ID" <| el [ alignLeft ] <| text <| "Node ID" - , el [ alignRight ] <| text <| stats.nodeId - ] - , wrappedRow [ width fill ] - [ withTooltip "Timestamp from when the connection was established at" <| el [ alignLeft ] <| text <| "Established At" - , el [ alignRight ] <| text <| timeToDateString stats.establishedAt timeZone - ] - , wrappedRow [ width fill ] - [ withTooltip "Timestamp of last time block was received from node if ever" <| el [ alignLeft ] <| text <| "Last Block Received" - , el [ alignRight ] <| text <| maybeTimeToDateString stats.lastBlockReceived timeZone - ] - , wrappedRow [ width fill ] - [ withTooltip "Timestamp of last time fragment was received from node if ever" <| el [ alignLeft ] <| text <| "Last Fragment Received" - , el [ alignRight ] <| text <| maybeTimeToDateString stats.lastFragmentReceived timeZone - ] - , wrappedRow [ width fill ] - [ withTooltip "Timestamp of last time gossip was received from node if ever" <| el [ alignLeft ] <| text <| "Last Fragment Received" - , el [ alignRight ] <| text <| maybeTimeToDateString stats.lastGossipReceived timeZone + , if showNetworkStats then + column + [ width fill + , spacing 10 + , padding 10 + , scrollbarY + , height (fill |> maximum 400) + ] + (List.map + (\stats -> + column [ width fill, padding 20, Background.color (Element.rgb255 240 241 242), Border.solid, Border.color (Element.rgb255 198 205 214), Border.widthEach { bottom = 0, left = 2, right = 0, top = 0 } ] + [ wrappedRow [ width fill ] + [ withTooltip "Hex-encoded node ID" <| el [ alignLeft ] <| text <| "Node ID" + , el [ alignRight ] <| text <| stats.nodeId + ] + , wrappedRow [ width fill ] + [ withTooltip "Timestamp from when the connection was established at" <| el [ alignLeft ] <| text <| "Established At" + , el [ alignRight ] <| text <| timeToDateString stats.establishedAt timeZone + ] + , wrappedRow [ width fill ] + [ withTooltip "Timestamp of last time block was received from node if ever" <| el [ alignLeft ] <| text <| "Last Block Received" + , el [ alignRight ] <| text <| maybeTimeToDateString stats.lastBlockReceived timeZone + ] + , wrappedRow [ width fill ] + [ withTooltip "Timestamp of last time fragment was received from node if ever" <| el [ alignLeft ] <| text <| "Last Fragment Received" + , el [ alignRight ] <| text <| maybeTimeToDateString stats.lastFragmentReceived timeZone + ] + , wrappedRow [ width fill ] + [ withTooltip "Timestamp of last time gossip was received from node if ever" <| el [ alignLeft ] <| text <| "Last Fragment Received" + , el [ alignRight ] <| text <| maybeTimeToDateString stats.lastGossipReceived timeZone + ] ] - ] + ) + networkStats ) - networkStats - ) - else - Element.none - ] + else + Element.none + ] -viewNodeSettings : NodeSettings -> Bool -> Time.Zone -> List (Element Msg) -viewNodeSettings nodeSettings showNodeSettings timeZone = - [ row [ width fill, Border.widthEach { bottom = 1, left = 0, right = 0, top = 0 }, paddingXY 2 10 ] - [ el [ alignLeft, fontNormal, Font.size 24 ] <| text <| "Node Settings" - , Input.button toggleButtonStyle { onPress = Just ToggleNodeSettingsPanel, label = toggleIcon showNodeSettings } +viewNodeSettings : Bool -> Time.Zone -> NodeSettings -> Element Msg +viewNodeSettings showNodeSettings timeZone nodeSettings = + column + [ Border.color (Element.rgb255 198 205 214) + , Border.width 2 + , padding 10 + , spacing 5 + , alignTop + , width fill ] - , if showNodeSettings then - column [ width fill, spacing 10, padding 10 ] - [ wrappedRow [ width fill ] - [ withTooltip "Hex-encoded hash of block0" <| el [ alignLeft ] <| text <| "Blocks Hash" - , el [ alignRight ] <| text <| nodeSettings.block0Hash - ] - , wrappedRow [ width fill ] - [ withTooltip "When block0 was created" <| el [ alignLeft ] <| text <| "Block Time" - , el [ alignRight ] <| text <| timeToDateString nodeSettings.block0Time timeZone - ] - , wrappedRow [ width fill ] - [ withTooltip "The block content's max size in bytes" <| el [ alignLeft ] <| text <| "Max Block Content" - , el [ alignRight ] <| text <| String.fromInt nodeSettings.blockContentMaxSize - ] - , wrappedRow [ width fill ] - [ withTooltip "Version of consensus, which is currently used" <| el [ alignLeft ] <| text <| "Consensus Version" - , el [ alignRight ] <| text <| nodeSettings.consensusVersion - ] - , wrappedRow [ width fill ] - [ withTooltip "When current slot was opened, not set if none is currently open" <| el [ alignLeft ] <| text <| "Current Slot Start Time" - , el [ alignRight ] <| text <| maybeTimeToDateString nodeSettings.currSlotStartTime timeZone - ] - , wrappedRow [ width fill ] - [ withTooltip "The depth, number of blocks, to which we consider the blockchain to be stable and prevent rollback beyond that depth" <| el [ alignLeft ] <| text <| "Stability Depth" - , el [ alignRight ] <| text <| String.fromInt nodeSettings.epochStabilityDepth - ] - , column [ width fill ] - [ withTooltip "Linear fees configuration" <| el [ alignLeft ] <| text <| "Fees" - , column [ width fill, spacing 10, padding 10 ] (viewNodeSettingsFees nodeSettings.fees) - ] - , column [ width fill ] - [ withTooltip "Parameters for rewards calculation" <| el [ alignLeft ] <| text <| "Rewards Parameters" - , column [ width fill, spacing 10, padding 10 ] (viewNodeSettingsRewardParams nodeSettings.rewardParams) - ] - , wrappedRow [ width fill ] - [ withTooltip "Slot duration in seconds" <| el [ alignLeft ] <| text <| "Slot Duration" - , el [ alignRight ] <| text <| String.fromInt nodeSettings.slotDuration - ] - , wrappedRow [ width fill ] - [ withTooltip "Number of slots per epoch" <| el [ alignLeft ] <| text <| "Slots Per Epoch" - , el [ alignRight ] <| text <| String.fromInt nodeSettings.slotsPerEpoch - ] - , column [ width fill ] - [ withTooltip "Tax from reward that goes to pot" <| el [ alignLeft ] <| text <| "Treasury Tax" - , column [ width fill, spacing 10, padding 10 ] (viewNodeSettingsTreasuryTax nodeSettings.treasuryTax) - ] + [ row [ width fill, Border.widthEach { bottom = 1, left = 0, right = 0, top = 0 }, paddingXY 2 10 ] + [ el [ alignLeft, fontNormal, Font.size 24 ] <| text <| "Node Settings" + , Input.button toggleButtonStyle { onPress = Just ToggleNodeSettingsPanel, label = toggleIcon showNodeSettings } ] + , if showNodeSettings then + column [ width fill, spacing 10, padding 10 ] + [ wrappedRow [ width fill ] + [ withTooltip "Hex-encoded hash of block0" <| el [ alignLeft ] <| text <| "Blocks Hash" + , el [ alignRight ] <| text <| nodeSettings.block0Hash + ] + , wrappedRow [ width fill ] + [ withTooltip "When block0 was created" <| el [ alignLeft ] <| text <| "Block Time" + , el [ alignRight ] <| text <| timeToDateString nodeSettings.block0Time timeZone + ] + , wrappedRow [ width fill ] + [ withTooltip "The block content's max size in bytes" <| el [ alignLeft ] <| text <| "Max Block Content" + , el [ alignRight ] <| text <| String.fromInt nodeSettings.blockContentMaxSize + ] + , wrappedRow [ width fill ] + [ withTooltip "Version of consensus, which is currently used" <| el [ alignLeft ] <| text <| "Consensus Version" + , el [ alignRight ] <| text <| nodeSettings.consensusVersion + ] + , wrappedRow [ width fill ] + [ withTooltip "When current slot was opened, not set if none is currently open" <| el [ alignLeft ] <| text <| "Current Slot Start Time" + , el [ alignRight ] <| text <| maybeTimeToDateString nodeSettings.currSlotStartTime timeZone + ] + , wrappedRow [ width fill ] + [ withTooltip "The depth, number of blocks, to which we consider the blockchain to be stable and prevent rollback beyond that depth" <| el [ alignLeft ] <| text <| "Stability Depth" + , el [ alignRight ] <| text <| String.fromInt nodeSettings.epochStabilityDepth + ] + , column [ width fill ] + [ withTooltip "Linear fees configuration" <| el [ alignLeft ] <| text <| "Fees" + , column [ width fill, spacing 10, padding 10 ] (viewNodeSettingsFees nodeSettings.fees) + ] + , column [ width fill ] + [ withTooltip "Parameters for rewards calculation" <| el [ alignLeft ] <| text <| "Rewards Parameters" + , column [ width fill, spacing 10, padding 10 ] (viewNodeSettingsRewardParams nodeSettings.rewardParams) + ] + , wrappedRow [ width fill ] + [ withTooltip "Slot duration in seconds" <| el [ alignLeft ] <| text <| "Slot Duration" + , el [ alignRight ] <| text <| String.fromInt nodeSettings.slotDuration + ] + , wrappedRow [ width fill ] + [ withTooltip "Number of slots per epoch" <| el [ alignLeft ] <| text <| "Slots Per Epoch" + , el [ alignRight ] <| text <| String.fromInt nodeSettings.slotsPerEpoch + ] + , column [ width fill ] + [ withTooltip "Tax from reward that goes to pot" <| el [ alignLeft ] <| text <| "Treasury Tax" + , column [ width fill, spacing 10, padding 10 ] (viewNodeSettingsTreasuryTax nodeSettings.treasuryTax) + ] + ] - else - Element.none - ] + else + Element.none + ] viewNodeSettingsFees : NodeSettingsFees -> List (Element Msg) @@ -2214,8 +2008,8 @@ viewStakePoolsSimple stakePoolsList = stakePoolsList -viewStakeDistribution : StakeDistribution -> Maybe StakePool -> String -> String -> Bool -> Bool -> Bool -> List (Element Msg) -viewStakeDistribution stakeDistribution stakePoolDetail stakePoolID myStakePoolID showZeroStaked sortDescending showSuggestions = +viewStakeDistribution : WebData StakePool -> String -> String -> Bool -> Bool -> Bool -> StakeDistribution -> Element Msg +viewStakeDistribution stakePoolDetail stakePoolID myStakePoolID showZeroStaked sortDescending showSuggestions stakeDistribution = let stakeDetail = stakeDistribution.stake @@ -2231,63 +2025,101 @@ viewStakeDistribution stakeDistribution stakePoolDetail stakePoolID myStakePoolI totalPools = List.length stakeDetail.pools + + cardanoExplorerLink = + if String.length stakePoolID == 64 then + newTabLink [ Font.color blue, fontMono ] + { url = "https://shelleyexplorer.cardano.org/en/stake-pool/" ++ stakePoolID, label = text "See on Explorer" } + + else + withTooltip "Invalid Stake Pool ID Length" <| el [ width fill ] <| text "See on Explorer" in - [ column [ width fill, spacing 10 ] - [ row [ Border.widthEach { bottom = 1, left = 0, right = 0, top = 0 }, width fill, paddingXY 2 10 ] - [ el [ alignLeft, fontNormal, Font.size 32 ] <| text <| "Stake Distribution" - ] + row + [ Font.size 14 + , fontMono + , spacing 15 + , padding 10 + , width fill ] - , wrappedRow [ width fill, padding 10, spacing 30 ] - [ column [ width (fillPortion 2), spacing 10, padding 10, alignTop, fontMono, Border.widthEach { bottom = 0, left = 0, right = 1, top = 0 }, Font.size 14 ] - [ row [ width fill ] - [ el [ alignLeft ] <| text <| "Stake Pools Staking" - , el [ alignRight ] <| text <| String.fromInt stakedPools - ] - , row [ width fill ] - [ el [ alignLeft ] <| text <| "Total Stake Pools" - , el [ alignRight ] <| text <| String.fromInt totalPools - ] - , column [ width fill, spacing 10 ] - [ row [ width fill ] - [ withTooltip "Epoch of last block" <| el [ alignLeft ] <| text <| "Epoch" - , el [ alignRight ] <| text <| String.fromInt stakeDistribution.epoch + -- [ case model.stakePoolsList of + -- Just stakePoolsList -> + -- viewPanel (viewStakePools stakePoolsList model.showStakePoolsList) + -- Nothing -> + -- Element.none + [ column [ centerX, width fill ] + [ column [ width fill, spacing 10 ] + [ row [ Border.widthEach { bottom = 1, left = 0, right = 0, top = 0 }, width fill, paddingXY 2 10 ] + [ el [ alignLeft, fontNormal, Font.size 32 ] <| text <| "Stake Distribution" ] - , column [ width fill, spacing 10 ] + ] + , wrappedRow [ width fill, padding 10, spacing 30 ] + [ column [ width (fillPortion 2), spacing 10, padding 10, alignTop, fontMono, Border.widthEach { bottom = 0, left = 0, right = 1, top = 0 }, Font.size 14 ] [ row [ width fill ] - [ withTooltip "Total value stored in accounts, but assigned to nonexistent pools" <| el [ alignLeft ] <| text <| "Dangling" - , row [ alignRight, spacing 5 ] [ adaIcon, el [ alignRight ] <| text <| formatAdaValue stakeDetail.dangling ] + [ el [ alignLeft ] <| text <| "Stake Pools Staking" + , el [ alignRight ] <| text <| String.fromInt stakedPools ] , row [ width fill ] - [ withTooltip "Total value stored in accounts, but not assigned to any pool" <| el [ alignLeft ] <| text <| "Unassigned" - , row [ alignRight, spacing 5 ] [ adaIcon, el [ alignRight ] <| text <| formatAdaValue stakeDetail.unassigned ] + [ el [ alignLeft ] <| text <| "Total Stake Pools" + , el [ alignRight ] <| text <| String.fromInt totalPools ] - , row [ width fill ] - [ withTooltip "Total value staked between pools" <| el [ width fill, alignLeft ] <| text <| "Total Staked" - , row [ alignRight, spacing 5 ] [ adaIcon, el [] <| text <| formatAdaValue stakedValue ] + , column [ width fill, spacing 10 ] + [ row [ width fill ] + [ withTooltip "Epoch of last block" <| el [ alignLeft ] <| text <| "Epoch" + , el [ alignRight ] <| text <| String.fromInt stakeDistribution.epoch + ] + , column [ width fill, spacing 10 ] + [ row [ width fill ] + [ withTooltip "Total value stored in accounts, but assigned to nonexistent pools" <| el [ alignLeft ] <| text <| "Dangling" + , row [ alignRight, spacing 5 ] [ adaIcon, el [ alignRight ] <| text <| formatAdaValue stakeDetail.dangling ] + ] + , row [ width fill ] + [ withTooltip "Total value stored in accounts, but not assigned to any pool" <| el [ alignLeft ] <| text <| "Unassigned" + , row [ alignRight, spacing 5 ] [ adaIcon, el [ alignRight ] <| text <| formatAdaValue stakeDetail.unassigned ] + ] + , row [ width fill ] + [ withTooltip "Total value staked between pools" <| el [ width fill, alignLeft ] <| text <| "Total Staked" + , row [ alignRight, spacing 5 ] [ adaIcon, el [] <| text <| formatAdaValue stakedValue ] + ] + , row [ width fill ] + [ withTooltip "Total value in circulation" <| el [ width fill, alignLeft ] <| text <| "Total" + , row [ alignRight, spacing 5 ] [ adaIcon, el [] <| text <| formatAdaValue totalValue ] + ] + ] ] - , row [ width fill ] - [ withTooltip "Total value in circulation" <| el [ width fill, alignLeft ] <| text <| "Total" - , row [ alignRight, spacing 5 ] [ adaIcon, el [] <| text <| formatAdaValue totalValue ] + , column [ Border.widthEach { bottom = 0, left = 0, right = 0, top = 1 }, width fill, paddingEach { bottom = 0, left = 0, right = 0, top = 10 }, spacing 10 ] + [ if showSuggestions then + row [ width fill, spacing 5 ] + [ Input.search [ centerY, Events.onFocus ActivateStakePoolInput, Events.onLoseFocus DeactivateStakePoolInput, inputSuggestions stakePoolID (List.map (\pool -> pool.id) stakeDistribution.stake.pools) StakePoolIdClicked ] + { onChange = StakePoolIdChanged + , text = stakePoolID + , placeholder = Maybe.Just (Input.placeholder [ centerY ] (text "Click a Stake Pool ID")) + , label = Input.labelLeft [ centerY ] (text "Stake Pool ID: ") + } + , cardanoExplorerLink + ] + + else + row [ width fill, spacing 5 ] + [ Input.search [ centerY, Events.onFocus ActivateStakePoolInput, Events.onLoseFocus DeactivateStakePoolInput ] + { onChange = StakePoolIdChanged + , text = stakePoolID + , placeholder = Maybe.Just (Input.placeholder [ centerY ] (text "Click a Stake Pool ID")) + , label = Input.labelLeft [ centerY ] (text "Stake Pool ID: ") + } + , cardanoExplorerLink + ] + , case stakePoolDetail of + NotAsked -> + el [ centerX ] <| text <| "Click a Stake Pool ID to see details " + + _ -> + webDataView viewStakePoolDetail stakePoolDetail ] ] - ] - , column [ Border.widthEach { bottom = 0, left = 0, right = 0, top = 1 }, width fill, paddingEach { bottom = 0, left = 0, right = 0, top = 10 }, spacing 10 ] - [ if showSuggestions then - Input.search [ centerY, Events.onFocus ActivateStakePoolInput, Events.onLoseFocus DeactivateStakePoolInput, inputSuggestions stakePoolID (List.map (\pool -> pool.id) stakeDistribution.stake.pools) StakePoolIdClicked ] { onChange = StakePoolIdChanged, text = stakePoolID, placeholder = Maybe.Just (Input.placeholder [ centerY ] (text "Click a Stake Pool ID")), label = Input.labelLeft [ centerY ] (text "Stake Pool ID: ") } - - else - Input.search [ centerY, Events.onFocus ActivateStakePoolInput, Events.onLoseFocus DeactivateStakePoolInput ] { onChange = StakePoolIdChanged, text = stakePoolID, placeholder = Maybe.Just (Input.placeholder [ centerY ] (text "Click a Stake Pool ID")), label = Input.labelLeft [ centerY ] (text "Stake Pool ID: ") } - , case stakePoolDetail of - Just spDetail -> - viewStakePoolDetail spDetail - - Nothing -> - el [ centerX ] <| text <| "Click a Stake Pool ID to see details " + , viewStakePoolsTable stakeDistribution.stake.pools showZeroStaked sortDescending myStakePoolID stakedValue ] ] - , viewStakePoolsTable stakeDistribution.stake.pools showZeroStaked sortDescending myStakePoolID stakedValue ] - ] inputSuggestions : String -> List String -> (String -> Msg) -> Element.Attribute Msg @@ -2530,8 +2362,8 @@ viewLeaders leadersList = leadersList -viewLeadersLogs : Posix -> List LeadersLogs -> Time.Zone -> Element Msg -viewLeadersLogs time leadersLogsList timeZone = +viewLeadersLogs : Posix -> Time.Zone -> List LeadersLogs -> Element Msg +viewLeadersLogs time timeZone leadersLogsList = let leadersLogsSorted = List.sortBy (\leader -> Time.posixToMillis leader.scheduled_at_time) leadersLogsList @@ -2988,6 +2820,11 @@ leadersSvg = loadingSvg : Element msg loadingSvg = + Element.html <| svg [ SvgA.width "122", SvgA.height "122", SvgA.viewBox "0 0 122 122", SvgA.version "1.1" ] [ g [ SvgA.id "Page-1", SvgA.stroke "none", SvgA.strokeWidth "1", SvgA.fill "none", SvgA.fillRule "evenodd" ] [ g [ SvgA.id "styles", SvgA.transform "translate(-860.000000, -5380.000000)", SvgA.fill "#FAFBFC" ] [ g [ SvgA.id "spinner-light", SvgA.transform "translate(860.000000, 5380.000000)" ] [ Svg.path [ SvgA.d "M18.5735931,18.5735931 C-4.85786437,42.0050506 -4.85786437,79.9949494 18.5735931,103.426407 C42.0050506,126.857864 79.9949494,126.857864 103.426407,103.426407 L97.7695526,97.7695526 C77.4622895,118.076816 44.5377105,118.076816 24.2304474,97.7695526 C3.92318421,77.4622895 3.92318421,44.5377105 24.2304474,24.2304474 L18.5735931,18.5735931 L18.5735931,18.5735931 L18.5735931,18.5735931 Z", SvgA.id "spinner" ] [] ] ] ] ] + + +fetchingSvg : Element msg +fetchingSvg = Element.html <| svg [ SvgA.width "30", SvgA.height "30", SvgA.viewBox "0 0 122 122", SvgA.version "1.1" ] [ g [ SvgA.id "Page-1", SvgA.stroke "none", SvgA.strokeWidth "1", SvgA.fill "none", SvgA.fillRule "evenodd" ] [ g [ SvgA.id "styles", SvgA.transform "translate(-860.000000, -5380.000000)", SvgA.fill "#FAFBFC" ] [ g [ SvgA.id "spinner-light", SvgA.transform "translate(860.000000, 5380.000000)" ] [ Svg.path [ SvgA.d "M18.5735931,18.5735931 C-4.85786437,42.0050506 -4.85786437,79.9949494 18.5735931,103.426407 C42.0050506,126.857864 79.9949494,126.857864 103.426407,103.426407 L97.7695526,97.7695526 C77.4622895,118.076816 44.5377105,118.076816 24.2304474,97.7695526 C3.92318421,77.4622895 3.92318421,44.5377105 24.2304474,24.2304474 L18.5735931,18.5735931 L18.5735931,18.5735931 L18.5735931,18.5735931 Z", SvgA.id "spinner" ] [] ] ] ] ]