diff --git a/cardano-tracer/bench/cardano-tracer-bench.hs b/cardano-tracer/bench/cardano-tracer-bench.hs
index 772c8af4cd3..a1d0b7ee021 100644
--- a/cardano-tracer/bench/cardano-tracer-bench.hs
+++ b/cardano-tracer/bench/cardano-tracer-bench.hs
@@ -11,6 +11,7 @@ import Cardano.Logging hiding (LocalSocket)
import Cardano.Tracer.Configuration
import Cardano.Tracer.Handlers.Logs.TraceObjects (traceObjectsHandler)
+import Cardano.Tracer.Handlers.RTView.State.TraceObjects (initSavedTraceObjects)
import Cardano.Tracer.Types (NodeId (..))
main :: IO ()
@@ -25,19 +26,21 @@ main = do
to100 <- generate 100
to1000 <- generate 1000
+ savedTO <- initSavedTraceObjects
+
removePathForcibly root
defaultMain
[ bgroup "cardano-tracer"
[ -- 10 'TraceObject's per request.
- bench "Handle TraceObjects LOG, 10" $ whnfIO $ traceObjectsHandler c1 nId lock to10
- , bench "Handle TraceObjects JSON, 10" $ whnfIO $ traceObjectsHandler c2 nId lock to10
+ bench "Handle TraceObjects LOG, 10" $ whnfIO $ traceObjectsHandler c1 nId lock savedTO to10
+ , bench "Handle TraceObjects JSON, 10" $ whnfIO $ traceObjectsHandler c2 nId lock savedTO to10
-- 100 'TraceObject's per request.
- , bench "Handle TraceObjects LOG, 100" $ whnfIO $ traceObjectsHandler c1 nId lock to100
- , bench "Handle TraceObjects JSON, 100" $ whnfIO $ traceObjectsHandler c2 nId lock to100
+ , bench "Handle TraceObjects LOG, 100" $ whnfIO $ traceObjectsHandler c1 nId lock savedTO to100
+ , bench "Handle TraceObjects JSON, 100" $ whnfIO $ traceObjectsHandler c2 nId lock savedTO to100
-- 1000 'TraceObject's per request.
- , bench "Handle TraceObjects LOG, 1000" $ whnfIO $ traceObjectsHandler c1 nId lock to1000
- , bench "Handle TraceObjects JSON, 1000" $ whnfIO $ traceObjectsHandler c2 nId lock to1000
+ , bench "Handle TraceObjects LOG, 1000" $ whnfIO $ traceObjectsHandler c1 nId lock savedTO to1000
+ , bench "Handle TraceObjects JSON, 1000" $ whnfIO $ traceObjectsHandler c2 nId lock savedTO to1000
]
]
where
@@ -50,6 +53,7 @@ main = do
, ekgRequestFreq = Nothing
, hasEKG = Nothing
, hasPrometheus = Nothing
+ , hasRTView = Nothing
, logging = NE.fromList [LoggingParams root FileMode format]
, rotation = Nothing
, verbosity = Nothing
diff --git a/cardano-tracer/cardano-tracer.cabal b/cardano-tracer/cardano-tracer.cabal
index 4483f3fbbf9..291f99036b3 100644
--- a/cardano-tracer/cardano-tracer.cabal
+++ b/cardano-tracer/cardano-tracer.cabal
@@ -45,6 +45,51 @@ library
Cardano.Tracer.Handlers.Metrics.Monitoring
Cardano.Tracer.Handlers.Metrics.Prometheus
Cardano.Tracer.Handlers.Metrics.Servers
+ Cardano.Tracer.Handlers.Metrics.Utils
+
+ Cardano.Tracer.Handlers.RTView.Run
+ Cardano.Tracer.Handlers.RTView.State.Displayed
+ Cardano.Tracer.Handlers.RTView.State.EraSettings
+ Cardano.Tracer.Handlers.RTView.State.Errors
+ Cardano.Tracer.Handlers.RTView.State.Historical
+ Cardano.Tracer.Handlers.RTView.State.Last
+ Cardano.Tracer.Handlers.RTView.State.Peers
+ Cardano.Tracer.Handlers.RTView.State.TraceObjects
+ Cardano.Tracer.Handlers.RTView.System
+ Cardano.Tracer.Handlers.RTView.UI.CSS.Bulma
+ Cardano.Tracer.Handlers.RTView.UI.CSS.Own
+ Cardano.Tracer.Handlers.RTView.UI.HTML.Node.Column
+ Cardano.Tracer.Handlers.RTView.UI.HTML.Node.EKG
+ Cardano.Tracer.Handlers.RTView.UI.HTML.Node.Errors
+ Cardano.Tracer.Handlers.RTView.UI.HTML.Node.Peers
+ Cardano.Tracer.Handlers.RTView.UI.HTML.About
+ Cardano.Tracer.Handlers.RTView.UI.HTML.Body
+ Cardano.Tracer.Handlers.RTView.UI.HTML.Main
+ Cardano.Tracer.Handlers.RTView.UI.HTML.NoNodes
+ Cardano.Tracer.Handlers.RTView.UI.HTML.Notifications
+ Cardano.Tracer.Handlers.RTView.UI.JS.ChartJS
+ Cardano.Tracer.Handlers.RTView.UI.JS.Charts
+ Cardano.Tracer.Handlers.RTView.UI.JS.Utils
+ Cardano.Tracer.Handlers.RTView.UI.Img.Icons
+ Cardano.Tracer.Handlers.RTView.UI.Charts
+ Cardano.Tracer.Handlers.RTView.UI.Theme
+ Cardano.Tracer.Handlers.RTView.UI.Types
+ Cardano.Tracer.Handlers.RTView.UI.Utils
+ Cardano.Tracer.Handlers.RTView.Update.Chain
+ Cardano.Tracer.Handlers.RTView.Update.EKG
+ Cardano.Tracer.Handlers.RTView.Update.EraSettings
+ Cardano.Tracer.Handlers.RTView.Update.Errors
+ Cardano.Tracer.Handlers.RTView.Update.Historical
+ Cardano.Tracer.Handlers.RTView.Update.KES
+ Cardano.Tracer.Handlers.RTView.Update.Leadership
+ Cardano.Tracer.Handlers.RTView.Update.NodeInfo
+ Cardano.Tracer.Handlers.RTView.Update.NodeState
+ Cardano.Tracer.Handlers.RTView.Update.Nodes
+ Cardano.Tracer.Handlers.RTView.Update.Peers
+ Cardano.Tracer.Handlers.RTView.Update.Reload
+ Cardano.Tracer.Handlers.RTView.Update.Resources
+ Cardano.Tracer.Handlers.RTView.Update.Transactions
+ Cardano.Tracer.Handlers.RTView.Update.Utils
Cardano.Tracer.CLI
Cardano.Tracer.Configuration
@@ -60,6 +105,8 @@ library
, blaze-html
, blaze-markup
, bytestring
+ , cardano-git-rev
+ , cardano-node
, cborg
, containers
, contra-tracer
@@ -76,6 +123,7 @@ library
, snap-core
, snap-server
, stm
+ , string-qq
, text
, threepenny-gui
, time
@@ -87,6 +135,11 @@ library
if os(linux)
build-depends: libsystemd-journal
+ if os(windows)
+ build-depends: Win32
+ else
+ build-depends: unix
+
executable cardano-tracer
import: base, project-config
diff --git a/cardano-tracer/src/Cardano/Tracer/Acceptors/Client.hs b/cardano-tracer/src/Cardano/Tracer/Acceptors/Client.hs
index a5ee740ceec..5c8ab69191d 100644
--- a/cardano-tracer/src/Cardano/Tracer/Acceptors/Client.hs
+++ b/cardano-tracer/src/Cardano/Tracer/Acceptors/Client.hs
@@ -40,6 +40,7 @@ import Cardano.Tracer.Acceptors.Utils (prepareDataPointRequestor,
prepareMetricsStores, removeDisconnectedNode)
import qualified Cardano.Tracer.Configuration as TC
import Cardano.Tracer.Handlers.Logs.TraceObjects (traceObjectsHandler)
+import Cardano.Tracer.Handlers.RTView.Run (SavedTraceObjects)
import Cardano.Tracer.Types (AcceptedMetrics, ConnectedNodes, DataPointRequestors)
import Cardano.Tracer.Utils (connIdToNodeId)
@@ -52,11 +53,12 @@ runAcceptorsClient
)
-> ConnectedNodes
-> AcceptedMetrics
+ -> SavedTraceObjects
-> DataPointRequestors
-> Lock
-> IO ()
runAcceptorsClient config p (ekgConfig, tfConfig, dpfConfig)
- connectedNodes acceptedMetrics dpRequestors currentLogLock =
+ connectedNodes acceptedMetrics savedTO dpRequestors currentLogLock =
withIOManager $ \iocp ->
doConnectToForwarder
(localSnocket iocp)
@@ -67,7 +69,7 @@ runAcceptorsClient config p (ekgConfig, tfConfig, dpfConfig)
-- there is no mechanism to disable some of them.
appInitiator
[ (runEKGAcceptorInit ekgConfig connectedNodes acceptedMetrics errorHandler, 1)
- , (runTraceObjectsAcceptorInit config tfConfig currentLogLock errorHandler, 2)
+ , (runTraceObjectsAcceptorInit config tfConfig currentLogLock savedTO errorHandler, 2)
, (runDataPointsAcceptorInit dpfConfig connectedNodes dpRequestors errorHandler, 3)
]
where
@@ -122,13 +124,14 @@ runTraceObjectsAcceptorInit
:: TC.TracerConfig
-> TF.AcceptorConfiguration TraceObject
-> Lock
+ -> SavedTraceObjects
-> (ConnectionId LocalAddress -> IO ())
-> ConnectionId LocalAddress
-> RunMiniProtocol 'InitiatorMode LBS.ByteString IO () Void
-runTraceObjectsAcceptorInit config tfConfig currentLogLock errorHandler connId =
+runTraceObjectsAcceptorInit config tfConfig currentLogLock savedTO errorHandler connId =
acceptTraceObjectsInit
tfConfig
- (traceObjectsHandler config (connIdToNodeId connId) currentLogLock)
+ (traceObjectsHandler config (connIdToNodeId connId) currentLogLock savedTO)
(errorHandler connId)
runDataPointsAcceptorInit
diff --git a/cardano-tracer/src/Cardano/Tracer/Acceptors/Run.hs b/cardano-tracer/src/Cardano/Tracer/Acceptors/Run.hs
index c992f580d7b..58f18eb46cc 100644
--- a/cardano-tracer/src/Cardano/Tracer/Acceptors/Run.hs
+++ b/cardano-tracer/src/Cardano/Tracer/Acceptors/Run.hs
@@ -23,6 +23,7 @@ import qualified Trace.Forward.Protocol.TraceObject.Type as TOF
import Cardano.Tracer.Acceptors.Client (runAcceptorsClient)
import Cardano.Tracer.Acceptors.Server (runAcceptorsServer)
import Cardano.Tracer.Configuration
+import Cardano.Tracer.Handlers.RTView.Run (SavedTraceObjects)
import Cardano.Tracer.Types (AcceptedMetrics, ConnectedNodes,
DataPointRequestors, ProtocolsBrake)
import Cardano.Tracer.Utils (runInLoop)
@@ -36,25 +37,26 @@ runAcceptors
:: TracerConfig
-> ConnectedNodes
-> AcceptedMetrics
+ -> SavedTraceObjects
-> DataPointRequestors
-> ProtocolsBrake
-> Lock
-> IO ()
runAcceptors c@TracerConfig{network, ekgRequestFreq, loRequestNum, verbosity}
- connectedNodes acceptedMetrics dpRequestors stopIt currentLogLock =
+ connectedNodes acceptedMetrics savedTO dpRequestors stopIt currentLogLock =
case network of
AcceptAt (LocalSocket p) ->
-- Run one server that accepts connections from the nodes.
runInLoop
(runAcceptorsServer c p (acceptorsConfigs p) connectedNodes
- acceptedMetrics dpRequestors currentLogLock)
+ acceptedMetrics savedTO dpRequestors currentLogLock)
verbosity p 1
ConnectTo localSocks ->
-- Run N clients that initiate connections to the nodes.
forConcurrently_ (NE.nub localSocks) $ \(LocalSocket p) ->
runInLoop
(runAcceptorsClient c p (acceptorsConfigs p) connectedNodes
- acceptedMetrics dpRequestors currentLogLock)
+ acceptedMetrics savedTO dpRequestors currentLogLock)
verbosity p 1
where
acceptorsConfigs p =
diff --git a/cardano-tracer/src/Cardano/Tracer/Acceptors/Server.hs b/cardano-tracer/src/Cardano/Tracer/Acceptors/Server.hs
index ad3ef2c5132..56813446cf9 100644
--- a/cardano-tracer/src/Cardano/Tracer/Acceptors/Server.hs
+++ b/cardano-tracer/src/Cardano/Tracer/Acceptors/Server.hs
@@ -43,6 +43,7 @@ import Cardano.Tracer.Acceptors.Utils (prepareDataPointRequestor,
prepareMetricsStores, removeDisconnectedNode)
import qualified Cardano.Tracer.Configuration as TC
import Cardano.Tracer.Handlers.Logs.TraceObjects (traceObjectsHandler)
+import Cardano.Tracer.Handlers.RTView.Run (SavedTraceObjects)
import Cardano.Tracer.Types (AcceptedMetrics, ConnectedNodes, DataPointRequestors)
import Cardano.Tracer.Utils (connIdToNodeId)
@@ -55,11 +56,12 @@ runAcceptorsServer
)
-> ConnectedNodes
-> AcceptedMetrics
+ -> SavedTraceObjects
-> DataPointRequestors
-> Lock
-> IO ()
-runAcceptorsServer config p (ekgConfig, tfConfig, dpfConfig)
- connectedNodes acceptedMetrics dpRequestors currentLogLock = withIOManager $ \iocp ->
+runAcceptorsServer config p (ekgConfig, tfConfig, dpfConfig) connectedNodes
+ acceptedMetrics savedTO dpRequestors currentLogLock = withIOManager $ \iocp ->
doListenToForwarder
(localSnocket iocp)
(localAddressFromPath p)
@@ -69,7 +71,7 @@ runAcceptorsServer config p (ekgConfig, tfConfig, dpfConfig)
-- there is no mechanism to disable some of them.
appResponder
[ (runEKGAcceptor ekgConfig connectedNodes acceptedMetrics errorHandler, 1)
- , (runTraceObjectsAcceptor config tfConfig currentLogLock errorHandler, 2)
+ , (runTraceObjectsAcceptor config tfConfig currentLogLock savedTO errorHandler, 2)
, (runDataPointsAcceptor dpfConfig connectedNodes dpRequestors errorHandler, 3)
]
where
@@ -129,13 +131,14 @@ runTraceObjectsAcceptor
:: TC.TracerConfig
-> TF.AcceptorConfiguration TraceObject
-> Lock
+ -> SavedTraceObjects
-> (ConnectionId LocalAddress -> IO ())
-> ConnectionId LocalAddress
-> RunMiniProtocol 'ResponderMode LBS.ByteString IO Void ()
-runTraceObjectsAcceptor config tfConfig currentLogLock errorHandler connId =
+runTraceObjectsAcceptor config tfConfig currentLogLock savedTO errorHandler connId =
acceptTraceObjectsResp
tfConfig
- (traceObjectsHandler config (connIdToNodeId connId) currentLogLock)
+ (traceObjectsHandler config (connIdToNodeId connId) currentLogLock savedTO)
(errorHandler connId)
runDataPointsAcceptor
diff --git a/cardano-tracer/src/Cardano/Tracer/CLI.hs b/cardano-tracer/src/Cardano/Tracer/CLI.hs
index 9b52703bce0..221f1886165 100644
--- a/cardano-tracer/src/Cardano/Tracer/CLI.hs
+++ b/cardano-tracer/src/Cardano/Tracer/CLI.hs
@@ -12,11 +12,11 @@ newtype TracerParams = TracerParams
-- | Parse CLI parameters for the tracer.
parseTracerParams :: Parser TracerParams
-parseTracerParams = TracerParams <$>
- strOption
- ( long "config"
- <> short 'c'
- <> metavar "FILEPATH"
- <> help "Configuration file for cardano-tracer"
- <> completer (bashCompleter "file")
- )
+parseTracerParams = TracerParams
+ <$> strOption
+ ( long "config"
+ <> short 'c'
+ <> metavar "FILEPATH"
+ <> help "Configuration file for cardano-tracer"
+ <> completer (bashCompleter "file")
+ )
diff --git a/cardano-tracer/src/Cardano/Tracer/Configuration.hs b/cardano-tracer/src/Cardano/Tracer/Configuration.hs
index 0b84afc6877..ecbdfbbed32 100644
--- a/cardano-tracer/src/Cardano/Tracer/Configuration.hs
+++ b/cardano-tracer/src/Cardano/Tracer/Configuration.hs
@@ -87,6 +87,7 @@ data TracerConfig = TracerConfig
, ekgRequestFreq :: !(Maybe Pico) -- ^ How often to request for EKG-metrics, in seconds.
, hasEKG :: !(Maybe (Endpoint, Endpoint)) -- ^ Endpoint for EKG web-page (list of nodes, monitoring).
, hasPrometheus :: !(Maybe Endpoint) -- ^ Endpoint for Promeheus web-page.
+ , hasRTView :: !(Maybe Endpoint) -- ^ Endpoint for RTView web-page.
, logging :: !(NonEmpty LoggingParams) -- ^ Logging parameters.
, rotation :: !(Maybe RotationParams) -- ^ Rotation parameters.
, verbosity :: !(Maybe Verbosity) -- ^ Verbosity of the tracer itself.
@@ -103,7 +104,7 @@ readTracerConfig pathToConfig =
Right _ -> return config
checkMeaninglessValues :: TracerConfig -> Either String ()
-checkMeaninglessValues TracerConfig{network, hasEKG, hasPrometheus, logging} =
+checkMeaninglessValues TracerConfig{network, hasEKG, hasPrometheus, hasRTView, logging} =
if null problems
then Right ()
else Left $ intercalate ", " problems
@@ -115,6 +116,7 @@ checkMeaninglessValues TracerConfig{network, hasEKG, hasPrometheus, logging} =
, check "empty logRoot(s)" $ notNull . NE.filter invalidFileMode $ logging
, (check "no host(s) in hasEKG" . nullEndpoints) =<< hasEKG
, (check "no host in hasPrometheus" . nullEndpoint) =<< hasPrometheus
+ , (check "no host in hasRTView" . nullEndpoint) =<< hasRTView
]
check msg cond = if cond then Just msg else Nothing
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/Logs/TraceObjects.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/Logs/TraceObjects.hs
index 82657b639da..b7a2e3ad6d8 100644
--- a/cardano-tracer/src/Cardano/Tracer/Handlers/Logs/TraceObjects.hs
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/Logs/TraceObjects.hs
@@ -6,6 +6,7 @@ module Cardano.Tracer.Handlers.Logs.TraceObjects
import Control.Concurrent.Async (forConcurrently_)
import Control.Concurrent.Extra (Lock)
+import Control.Monad.Extra (whenJust)
import qualified Data.List.NonEmpty as NE
import Cardano.Logging (TraceObject)
@@ -13,21 +14,26 @@ import Cardano.Logging (TraceObject)
import Cardano.Tracer.Configuration
import Cardano.Tracer.Handlers.Logs.File (writeTraceObjectsToFile)
import Cardano.Tracer.Handlers.Logs.Journal (writeTraceObjectsToJournal)
+import Cardano.Tracer.Handlers.RTView.Run (SavedTraceObjects, saveTraceObjects)
import Cardano.Tracer.Types (NodeId)
import Cardano.Tracer.Utils (showProblemIfAny)
-- | This handler is called periodically by 'TraceObjectForward' protocol
-- from 'trace-forward' library.
traceObjectsHandler
- :: TracerConfig -- ^ Tracer configuration.
- -> NodeId -- ^ An id of the node 'TraceObject's were received from.
- -> Lock -- ^ The lock we use for single-threaded access to the current log.
- -> [TraceObject] -- ^ The list of received 'TraceObject's (may be empty).
+ :: TracerConfig -- ^ Tracer configuration.
+ -> NodeId -- ^ An id of the node 'TraceObject's were received from.
+ -> Lock -- ^ The lock we use for single-threaded access to the current log.
+ -> SavedTraceObjects -- ^ The buffer for accepted 'TraceObject's, used by RTView service.
+ -> [TraceObject] -- ^ The list of received 'TraceObject's (may be empty).
-> IO ()
-traceObjectsHandler _ _ _ [] = return ()
-traceObjectsHandler TracerConfig{logging, verbosity} nodeId currentLogLock traceObjects =
+traceObjectsHandler _ _ _ _ [] = return ()
+traceObjectsHandler TracerConfig{logging, verbosity, hasRTView}
+ nodeId currentLogLock savedTO traceObjects = do
forConcurrently_ (NE.nub logging) $ \LoggingParams{logMode, logRoot, logFormat} ->
showProblemIfAny verbosity $
case logMode of
FileMode -> writeTraceObjectsToFile nodeId currentLogLock logRoot logFormat traceObjects
JournalMode -> writeTraceObjectsToJournal nodeId traceObjects
+ whenJust hasRTView . const $
+ saveTraceObjects savedTO nodeId traceObjects
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Monitoring.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Monitoring.hs
index 141f3c79761..54a942aa709 100644
--- a/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Monitoring.hs
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Monitoring.hs
@@ -20,6 +20,7 @@ import Data.Text.Encoding (encodeUtf8)
import qualified Graphics.UI.Threepenny as UI
import Graphics.UI.Threepenny.Core (UI, Element, liftIO, set, (#), (#+))
import System.Remote.Monitoring (forkServerWith, serverThreadId)
+import System.Time.Extra (sleep)
import Cardano.Tracer.Configuration (Endpoint (..))
import Cardano.Tracer.Types (AcceptedMetrics, ConnectedNodes, NodeId (..))
@@ -39,7 +40,9 @@ runMonitoringServer
-> ConnectedNodes
-> AcceptedMetrics
-> IO ()
-runMonitoringServer (Endpoint listHost listPort, monitorEP) connectedNodes acceptedMetrics =
+runMonitoringServer (Endpoint listHost listPort, monitorEP) connectedNodes acceptedMetrics = do
+ -- Pause to prevent collision between "Listening"-notifications from servers.
+ sleep 0.2
UI.startGUI config $ \window -> do
void $ return window # set UI.title "EKG Monitoring Nodes"
void $ mkPageBody window connectedNodes monitorEP acceptedMetrics
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Prometheus.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Prometheus.hs
index e03dec4734c..102d62b0902 100644
--- a/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Prometheus.hs
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Prometheus.hs
@@ -55,6 +55,8 @@ runPrometheusServer
-> AcceptedMetrics
-> IO ()
runPrometheusServer (Endpoint host port) connectedNodes acceptedMetrics = forever $ do
+ -- Pause to prevent collision between "Listening"-notifications from servers.
+ sleep 0.1
-- If everything is okay, the function 'simpleHttpServe' never returns.
-- But if there is some problem, it never throws an exception, but just stops.
-- So if it stopped - it will be re-started.
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Utils.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Utils.hs
new file mode 100644
index 00000000000..5f786d2c0f7
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Utils.hs
@@ -0,0 +1,26 @@
+module Cardano.Tracer.Handlers.Metrics.Utils
+ ( MetricName
+ , MetricValue
+ , MetricsList
+ , getListOfMetrics
+ ) where
+
+import qualified Data.HashMap.Strict as HM
+import Data.Maybe (mapMaybe)
+import Data.Text (Text)
+import qualified Data.Text as T
+import System.Metrics (Store, Value (..), sampleAll)
+
+type MetricName = Text
+type MetricValue = Text
+type MetricsList = [(MetricName, MetricValue)]
+
+getListOfMetrics :: Store -> IO MetricsList
+getListOfMetrics = fmap (mapMaybe metricsWeNeed . HM.toList) . sampleAll
+ where
+ metricsWeNeed (mName, mValue) =
+ case mValue of
+ Counter c -> Just (mName, T.pack $ show c)
+ Gauge g -> Just (mName, T.pack $ show g)
+ Label l -> Just (mName, l)
+ _ -> Nothing -- 'ekg-forward' doesn't support 'Distribution' yet.
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Run.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Run.hs
new file mode 100644
index 00000000000..3c054a50f1c
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Run.hs
@@ -0,0 +1,103 @@
+{-# LANGUAGE NamedFieldPuns #-}
+
+module Cardano.Tracer.Handlers.RTView.Run
+ ( runRTView
+ , module Cardano.Tracer.Handlers.RTView.State.TraceObjects
+ ) where
+
+import Control.Concurrent.Async.Extra (sequenceConcurrently)
+import Control.Monad (void)
+import Control.Monad.Extra (whenJust)
+import qualified Data.Text as T
+import Data.Text.Encoding (encodeUtf8)
+import qualified Graphics.UI.Threepenny as UI
+import System.Time.Extra (sleep)
+
+import Cardano.Tracer.Configuration
+import Cardano.Tracer.Handlers.RTView.State.EraSettings
+import Cardano.Tracer.Handlers.RTView.State.Displayed
+import Cardano.Tracer.Handlers.RTView.State.Errors
+import Cardano.Tracer.Handlers.RTView.State.Historical
+import Cardano.Tracer.Handlers.RTView.State.Last
+import Cardano.Tracer.Handlers.RTView.State.TraceObjects
+import Cardano.Tracer.Handlers.RTView.UI.HTML.Main
+import Cardano.Tracer.Handlers.RTView.Update.EraSettings
+import Cardano.Tracer.Handlers.RTView.Update.Errors
+import Cardano.Tracer.Handlers.RTView.Update.Historical
+import Cardano.Tracer.Types
+
+-- | RTView is a part of 'cardano-tracer' that provides an ability
+-- to monitor Cardano nodes in a real-time. The core idea is simple:
+-- RTView periodically receives some informations from the connected
+-- node(s) and displays that information on a web-page.
+--
+-- The web-page is built using 'threepenny-gui' library. Please note
+-- Gitub-version of this library is used, not Hackage-version!
+--
+-- TODO ...
+
+runRTView
+ :: TracerConfig
+ -> ConnectedNodes
+ -> AcceptedMetrics
+ -> SavedTraceObjects
+ -> DataPointRequestors
+ -> IO ()
+runRTView TracerConfig{logging, network, hasRTView}
+ connectedNodes acceptedMetrics savedTO dpRequestors =
+ whenJust hasRTView $ \(Endpoint host port) -> do
+ -- Pause to prevent collision between "Listening"-notifications from servers.
+ sleep 0.3
+ -- Initialize displayed stuff outside of main page renderer,
+ -- to be able to update corresponding elements after page reloading.
+ displayedElements <- initDisplayedElements
+ reloadFlag <- initPageReloadFlag
+ -- We have to collect different information from the node and save it
+ -- independently from RTView web-server. As a result, we'll be able to
+ -- show charts with historical data (where X axis is the time) for the
+ -- period when RTView web-page wasn't opened.
+ resourcesHistory <- initResourcesHistory
+ lastResources <- initLastResources
+ chainHistory <- initBlockchainHistory
+ txHistory <- initTransactionsHistory
+ eraSettings <- initErasSettings
+ errors <- initErrors
+
+ void . sequenceConcurrently $
+ [ UI.startGUI (config host port) $
+ mkMainPage
+ connectedNodes
+ displayedElements
+ acceptedMetrics
+ savedTO
+ eraSettings
+ dpRequestors
+ reloadFlag
+ logging
+ network
+ resourcesHistory
+ chainHistory
+ txHistory
+ errors
+ , runHistoricalUpdater
+ savedTO
+ acceptedMetrics
+ resourcesHistory
+ lastResources
+ chainHistory
+ txHistory
+ , runEraSettingsUpdater
+ connectedNodes
+ eraSettings
+ savedTO
+ , runErrorsUpdater
+ connectedNodes
+ errors
+ savedTO
+ ]
+ where
+ config h p = UI.defaultConfig
+ { UI.jsPort = Just . fromIntegral $ p
+ , UI.jsAddr = Just . encodeUtf8 . T.pack $ h
+ , UI.jsLog = const $ return () -- To hide 'threepenny-gui' internal messages.
+ }
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Displayed.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Displayed.hs
new file mode 100644
index 00000000000..d3aaee799d2
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Displayed.hs
@@ -0,0 +1,125 @@
+module Cardano.Tracer.Handlers.RTView.State.Displayed
+ ( DisplayedElements
+ , PageReloadedFlag
+ , getDisplayedValue
+ , getDisplayedValuePure
+ , initDisplayedElements
+ , initPageReloadFlag
+ , pageWasReload
+ , pageWasNotReload
+ , saveDisplayedValue
+ , updateDisplayedElements
+ ) where
+
+import Control.Concurrent.STM (atomically)
+import Control.Concurrent.STM.TVar (TVar, modifyTVar', newTVarIO, readTVarIO)
+import Data.List ((\\))
+import Data.Map.Strict (Map)
+import qualified Data.Map.Strict as M
+import Data.Set (Set)
+import qualified Data.Set as S
+import Data.Text (Text)
+
+import Cardano.Tracer.Types (NodeId)
+
+type ElementId = Text
+type ElementValue = Text
+
+-- | We store all currently displayed values for all elements in each node panel.
+--
+-- There are 2 reasons for it:
+--
+-- 1. If received 'TraceObject' contains the data with the same value again, we
+-- won't update corresponding element, to keep web-traffic.
+-- 2. If the user reloaded the page, all previously displayed elements will be
+-- updated in the same state again.
+--
+type DisplayedForNode = Map ElementId ElementValue
+type DisplayedElements = TVar (Map NodeId DisplayedForNode)
+
+initDisplayedElements :: IO DisplayedElements
+initDisplayedElements = newTVarIO M.empty
+
+getDisplayedValue
+ :: DisplayedElements
+ -> NodeId
+ -> ElementId
+ -> IO (Maybe ElementValue)
+getDisplayedValue displayedElements nodeId elId =
+ maybe Nothing (M.lookup elId) . M.lookup nodeId <$> readTVarIO displayedElements
+
+getDisplayedValuePure
+ :: Map NodeId DisplayedForNode
+ -> NodeId
+ -> ElementId
+ -> Maybe ElementValue
+getDisplayedValuePure displayed nodeId elId =
+ maybe Nothing (M.lookup elId) . M.lookup nodeId $ displayed
+
+saveDisplayedValue
+ :: DisplayedElements
+ -> NodeId
+ -> ElementId
+ -> ElementValue
+ -> IO ()
+saveDisplayedValue displayedElements nodeId elId elValue = atomically $
+ modifyTVar' displayedElements $ \currentDisplayedEls ->
+ case M.lookup nodeId currentDisplayedEls of
+ Nothing -> M.insert nodeId (M.singleton elId elValue) currentDisplayedEls
+ Just elsForNode ->
+ let elsForNode' =
+ case M.lookup elId elsForNode of
+ Nothing -> M.insert elId elValue elsForNode
+ Just _ -> M.adjust (const elValue) elId elsForNode
+ in M.adjust (const elsForNode') nodeId currentDisplayedEls
+
+-- | Makes 'displayedElements' up-to-date with 'connected'. There are 3 cases:
+--
+-- 1. 'displayedElements' contains all node ids from 'connected'
+-- - just keep it.
+-- 2. 'displayedElements' contains some node ids that is not presented in 'connected'
+-- - remove them, these ids correspond to disconnected nodes.
+-- 3. 'displayedElements' doesn't contain some node ids that presented in 'connected'
+-- - add them, these ids correspond to newly connected nodes.
+--
+-- 2-nd and 3-d cases can coexist.
+--
+updateDisplayedElements
+ :: DisplayedElements
+ -> Set NodeId
+ -> IO ()
+updateDisplayedElements displayedElements connected = atomically $
+ modifyTVar' displayedElements $ \currentDisplayedEls ->
+ let connectedIds = S.toList connected
+ displayedIds = M.keys currentDisplayedEls
+ in if displayedIds == connectedIds
+ then currentDisplayedEls -- 1-st case.
+ else
+ let disconnectedIds = displayedIds \\ connectedIds -- In 'displayedIds' but not in 'connectedIds'.
+ newlyConnectedIds = connectedIds \\ displayedIds -- In 'connectedIds' but not in 'displayedIds'.
+ withoutDisconnected = deleteDisconnected disconnectedIds currentDisplayedEls
+ in addNewlyConnected newlyConnectedIds withoutDisconnected
+ where
+ deleteDisconnected = go
+ where
+ go [] els = els
+ go (anId:ids) els = go ids $ M.delete anId els
+
+ addNewlyConnected = go
+ where
+ go [] els = els
+ go (anId:ids) els = go ids $ M.insert anId M.empty els
+
+-- | If the user reloaded the web-page, after DOM re-rendering, we have to restore
+-- displayed state of all elements that they have _before_ page's reload.
+type PageReloadedFlag = TVar Bool
+
+initPageReloadFlag :: IO PageReloadedFlag
+initPageReloadFlag = newTVarIO True
+
+pageWasReload, pageWasNotReload :: PageReloadedFlag -> IO ()
+pageWasReload = setFlag True
+pageWasNotReload = setFlag False
+
+setFlag :: Bool -> PageReloadedFlag -> IO ()
+setFlag state flag = atomically . modifyTVar' flag . const $ state
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/EraSettings.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/EraSettings.hs
new file mode 100644
index 00000000000..173711974e8
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/EraSettings.hs
@@ -0,0 +1,44 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Cardano.Tracer.Handlers.RTView.State.EraSettings
+ ( EraSettings (..)
+ , ErasSettings
+ , addEraSettings
+ , initErasSettings
+ ) where
+
+import Control.Concurrent.STM (atomically)
+import Control.Concurrent.STM.TVar (TVar, modifyTVar', newTVarIO)
+import Data.Map.Strict (Map)
+import qualified Data.Map.Strict as M
+import Data.Text (Text)
+
+import Cardano.Tracer.Types (NodeId)
+
+data EraSettings = EraSettings
+ { esEra :: !Text
+ , esSlotLengthInS :: !Int
+ , esEpochLength :: !Int
+ , esKESPeriodLength :: !Int
+ } deriving (Eq, Show)
+
+type ErasSettings = TVar (Map NodeId EraSettings)
+
+initErasSettings :: IO ErasSettings
+initErasSettings = newTVarIO M.empty
+
+addEraSettings
+ :: ErasSettings
+ -> NodeId
+ -> EraSettings
+ -> IO ()
+addEraSettings nodesSettings nodeId settingsForIt = atomically $
+ modifyTVar' nodesSettings $ \currentSettings ->
+ case M.lookup nodeId currentSettings of
+ Nothing ->
+ M.insert nodeId settingsForIt currentSettings
+ Just savedSettings ->
+ -- The settings for the same era shouldn't be changed.
+ if esEra savedSettings == esEra settingsForIt
+ then currentSettings
+ else M.adjust (const settingsForIt) nodeId currentSettings
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Errors.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Errors.hs
new file mode 100644
index 00000000000..bfdca0ed55d
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Errors.hs
@@ -0,0 +1,132 @@
+{-# LANGUAGE LambdaCase #-}
+
+module Cardano.Tracer.Handlers.RTView.State.Errors
+ ( ErrorIx
+ , ErrorInfo
+ , Errors
+ , addError
+ , getError
+ , getErrors
+ , getErrorsFilteredBySeverity
+ , getErrorsFilteredByText
+ , getErrorsSortedBy
+ , timeAsc
+ , timeDesc
+ , severityAsc
+ , severityDesc
+ , initErrors
+ , deleteAllErrors
+ ) where
+
+import Control.Concurrent.STM (atomically)
+import Control.Concurrent.STM.TVar
+import Data.List (find, sortBy)
+import Data.Map.Strict (Map)
+import qualified Data.Map.Strict as M
+import Data.Text (Text, isInfixOf)
+import qualified Data.Text as T
+
+import Cardano.Tracer.Handlers.RTView.State.TraceObjects
+import Cardano.Tracer.Types (NodeId)
+
+import Cardano.Logging (SeverityS)
+
+type ErrorIx = Int
+type ErrorInfo = (ErrorIx, TraceObjectInfo)
+type Errors = TVar (Map NodeId [ErrorInfo])
+
+initErrors :: IO Errors
+initErrors = newTVarIO M.empty
+
+addError
+ :: Errors
+ -> NodeId
+ -> TraceObjectInfo
+ -> IO ()
+addError errors nodeId trObInfo = atomically $
+ modifyTVar' errors $ \currentErrors ->
+ case M.lookup nodeId currentErrors of
+ Nothing -> do
+ let errorIx = 0
+ M.insert nodeId [(errorIx, trObInfo)] currentErrors
+ Just errorsFromNode -> do
+ -- All errors here should be unique, so check it first.
+ case find (\(_, trObInfo') -> trObInfo == trObInfo') errorsFromNode of
+ Nothing -> do
+ -- No such error, add it.
+ let errorIx = length errorsFromNode
+ M.adjust (const $ errorsFromNode ++ [(errorIx, trObInfo)]) nodeId currentErrors
+ Just _ -> currentErrors
+
+deleteAllErrors
+ :: Errors
+ -> NodeId
+ -> IO ()
+deleteAllErrors errors nodeId = atomically $
+ modifyTVar' errors $ \currentErrors ->
+ case M.lookup nodeId currentErrors of
+ Nothing -> currentErrors
+ Just _ -> M.adjust (const []) nodeId currentErrors
+
+getError
+ :: ErrorIx
+ -> Errors
+ -> NodeId
+ -> IO (Maybe ErrorInfo)
+getError errorIx errors nodeId =
+ getErrors errors nodeId >>= \case
+ [] -> return Nothing
+ allErrors -> return $ find (\(ix, _) -> ix == errorIx) allErrors
+
+getErrors
+ :: Errors
+ -> NodeId
+ -> IO [ErrorInfo]
+getErrors errors nodeId =
+ getErrorsHandled errors nodeId id
+
+getErrorsSortedBy
+ :: (ErrorInfo -> ErrorInfo -> Ordering)
+ -> Errors
+ -> NodeId
+ -> IO [ErrorInfo]
+getErrorsSortedBy ordering errors nodeId =
+ getErrorsHandled errors nodeId $ sortBy ordering
+
+timeAsc
+ , timeDesc
+ , severityAsc
+ , severityDesc :: ErrorInfo -> ErrorInfo -> Ordering
+timeAsc (_, (_, _, ts1)) (_, (_, _, ts2)) = ts1 `compare` ts2
+timeDesc (_, (_, _, ts1)) (_, (_, _, ts2)) = ts2 `compare` ts1
+severityAsc (_, (_, s1, _)) (_, (_, s2, _)) = s1 `compare` s2
+severityDesc (_, (_, s1, _)) (_, (_, s2, _)) = s2 `compare` s1
+
+getErrorsFilteredBySeverity
+ :: SeverityS
+ -> Errors
+ -> NodeId
+ -> IO [ErrorInfo]
+getErrorsFilteredBySeverity severity errors nodeId =
+ getErrorsHandled errors nodeId $ filter (\(_, (_, sev, _)) -> sev == severity)
+
+getErrorsFilteredByText
+ :: Text
+ -> Errors
+ -> NodeId
+ -> IO [ErrorInfo]
+getErrorsFilteredByText textToSearch errors nodeId =
+ if T.null textToSearch
+ then return []
+ else getErrorsHandled errors nodeId $ filter (\(_, (msg, _, _)) -> textToSearch `isInfixOf` msg)
+
+getErrorsHandled
+ :: Errors
+ -> NodeId
+ -> ([ErrorInfo] -> [ErrorInfo])
+ -> IO [ErrorInfo]
+getErrorsHandled errors nodeId handler = do
+ errors' <- readTVarIO errors
+ case M.lookup nodeId errors' of
+ Nothing -> return []
+ Just errorsFromNode -> return $ handler errorsFromNode
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Historical.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Historical.hs
new file mode 100644
index 00000000000..6d6bdf7123b
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Historical.hs
@@ -0,0 +1,204 @@
+{-# LANGUAGE BangPatterns #-}
+
+module Cardano.Tracer.Handlers.RTView.State.Historical
+ ( BlockchainHistory (..)
+ , DataName (..)
+ , History
+ , HistoricalPoint
+ , POSIXTime
+ , ResourcesHistory (..)
+ , TransactionsHistory (..)
+ , ValueH (..)
+ , addHistoricalData
+ , getHistoricalData
+ , initBlockchainHistory
+ , initResourcesHistory
+ , initTransactionsHistory
+ , readValueI
+ , readValueD
+ ) where
+
+import Control.Concurrent.STM (atomically)
+import Control.Concurrent.STM.TVar (TVar, modifyTVar', newTVarIO, readTVarIO)
+import Data.Map.Strict (Map)
+import qualified Data.Map.Strict as M
+import Data.Set (Set)
+import qualified Data.Set as S
+import Data.Text (Text)
+import Data.Text.Read
+import Data.Time.Clock (UTCTime)
+import Data.Word (Word64)
+
+import Cardano.Tracer.Handlers.RTView.Update.Utils
+import Cardano.Tracer.Types (NodeId)
+
+-- | A lot of information received from the node is useful as historical data.
+-- It means that such an information should be displayed on time charts,
+-- where X axis is a time in UTC. An example: resource metrics, chain information,
+-- tx information, etc.
+type POSIXTime = Word64
+
+data ValueH
+ = ValueD Double
+ | ValueI Int
+ deriving (Eq, Ord)
+
+instance Show ValueH where
+ show (ValueD d) = show d
+ show (ValueI i) = show i
+
+instance Num ValueH where
+ (+) (ValueI i1) (ValueI i2) = ValueI (i1 + i2)
+ (+) (ValueD d1) (ValueD d2) = ValueD (d1 + d2)
+ (+) (ValueI i1) (ValueD d1) = ValueD (fromIntegral i1 + d1)
+ (+) (ValueD d1) (ValueI i1) = ValueD (fromIntegral i1 + d1)
+
+ (-) (ValueI i1) (ValueI i2) = ValueI (i1 - i2)
+ (-) (ValueD d1) (ValueD d2) = ValueD (d1 - d2)
+ (-) (ValueI i1) (ValueD d1) = ValueD (fromIntegral i1 - d1)
+ (-) (ValueD d1) (ValueI i1) = ValueD (fromIntegral i1 - d1)
+
+ (*) (ValueI i1) (ValueI i2) = ValueI (i1 * i2)
+ (*) (ValueD d1) (ValueD d2) = ValueD (d1 * d2)
+ (*) (ValueI i1) (ValueD d1) = ValueD (fromIntegral i1 * d1)
+ (*) (ValueD d1) (ValueI i1) = ValueD (fromIntegral i1 * d1)
+
+ abs (ValueI i) = ValueI (abs i)
+ abs (ValueD d) = ValueD (abs d)
+
+ signum (ValueI i) = ValueI (signum i)
+ signum (ValueD d) = ValueD (signum d)
+
+ fromInteger i = ValueI (fromInteger i)
+
+type HistoricalPoint = (POSIXTime, ValueH)
+
+type HistoricalPoints = Set HistoricalPoint
+
+-- | Historical points for particular data.
+data DataName
+ = CPUData
+ | MemoryData
+ | GCMajorNumData
+ | GCMinorNumData
+ | GCLiveMemoryData
+ | CPUTimeGCData
+ | CPUTimeAppData
+ | ThreadsNumData
+ -- Chain
+ | ChainDensityData
+ | SlotNumData
+ | BlockNumData
+ | SlotInEpochData
+ | EpochData
+ | NodeCannotForgeData
+ | ForgedSlotLastData
+ | NodeIsLeaderData
+ | NodeIsNotLeaderData
+ | ForgedInvalidSlotLastData
+ | AdoptedSlotLastData
+ | NotAdoptedSlotLastData
+ | AboutToLeadSlotLastData
+ | CouldNotForgeSlotLastData
+ -- TX
+ | TxsProcessedNumData
+ | MempoolBytesData
+ | TxsInMempoolData
+ deriving (Eq, Ord)
+
+type HistoricalData = Map DataName HistoricalPoints
+type History = TVar (Map NodeId HistoricalData)
+
+newtype BlockchainHistory = ChainHistory History
+newtype ResourcesHistory = ResHistory History
+newtype TransactionsHistory = TXHistory History
+
+initBlockchainHistory :: IO BlockchainHistory
+initBlockchainHistory = ChainHistory <$> newTVarIO M.empty
+
+initResourcesHistory :: IO ResourcesHistory
+initResourcesHistory = ResHistory <$> newTVarIO M.empty
+
+initTransactionsHistory :: IO TransactionsHistory
+initTransactionsHistory = TXHistory <$> newTVarIO M.empty
+
+addHistoricalData
+ :: History
+ -> NodeId
+ -> UTCTime
+ -> DataName
+ -> ValueH
+ -> IO ()
+addHistoricalData history nodeId now dataName valueH = atomically $
+ modifyTVar' history $ \currentHistory ->
+ case M.lookup nodeId currentHistory of
+ Nothing ->
+ -- There is no historical data for this node yet.
+ let firstPoint = S.singleton (utc2s now, valueH)
+ newDataForNode = M.singleton dataName firstPoint
+ in M.insert nodeId newDataForNode currentHistory
+ Just dataForNode ->
+ let newDataForNode =
+ case M.lookup dataName dataForNode of
+ Nothing ->
+ -- There is no historical points for this dataName yet.
+ let firstPoint = S.singleton (utc2s now, valueH)
+ in M.insert dataName firstPoint dataForNode
+ Just points ->
+ let pointsWeKeep = S.fromList . deleteOutdated . S.toAscList $ points
+ newPoints = S.insert (utc2s now, valueH) pointsWeKeep
+ in M.adjust (const newPoints) dataName dataForNode
+ in M.adjust (const newDataForNode) nodeId currentHistory
+ where
+ -- All points that older than 'minAge' should be deleted.
+ deleteOutdated = go
+ where
+ go [] = []
+ go (point@(tsInSec, _):otherPoints) =
+ if tsInSec < minAge
+ then
+ -- This point is too old, do not keep it anymore.
+ go otherPoints
+ else
+ -- This point should be kept.
+ -- Since the points were converted to asc list, all the next points
+ -- are definitely newer (have bigger 'tsInSec'), so there is no need
+ -- to check them.
+ point : otherPoints
+
+ !minAge = utc2s now - pointsAgeInSec
+ pointsAgeInSec = 12 * 60 * 60
+
+getHistoricalData
+ :: History
+ -> NodeId
+ -> DataName
+ -> IO [(POSIXTime, ValueH)]
+getHistoricalData history nodeId dataName = do
+ history' <- readTVarIO history
+ case M.lookup nodeId history' of
+ Nothing -> return []
+ Just dataForNode ->
+ case M.lookup dataName dataForNode of
+ Nothing -> return []
+ Just points -> return $ S.toAscList points
+
+readValueI
+ :: Monad m
+ => Text
+ -> (ValueH -> m ())
+ -> m ()
+readValueI t f =
+ case decimal t of
+ Left _ -> return ()
+ Right (i, _) -> f (ValueI i)
+
+readValueD
+ :: Monad m
+ => Text
+ -> (ValueH -> m ())
+ -> m ()
+readValueD t f =
+ case double t of
+ Left _ -> return ()
+ Right (d, _) -> f (ValueD d)
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Last.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Last.hs
new file mode 100644
index 00000000000..14bf3c12ce5
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Last.hs
@@ -0,0 +1,53 @@
+module Cardano.Tracer.Handlers.RTView.State.Last
+ ( LastResourcesForNode (..)
+ , LastResources
+ , addNullResources
+ , initLastResources
+ , updateLastResources
+ ) where
+
+import Control.Concurrent.STM (atomically)
+import Control.Concurrent.STM.TVar (TVar, modifyTVar', newTVarIO)
+import Data.Map.Strict (Map)
+import qualified Data.Map.Strict as M
+import Data.Word (Word64)
+
+import Cardano.Tracer.Types (NodeId)
+
+-- | We have to store last received metric to be able to calculate
+-- the value based on next received metric. For example, calculate
+-- "CPU usage percent" based on received CPU ticks.
+-- That value will be stored in corresponding 'HistoricalData' for
+-- rendering on the corresponding chart.
+
+data LastResourcesForNode = LastResourcesForNode
+ { cpuLastTicks :: !Int
+ , cpuLastNS :: !Word64
+ }
+
+type LastResources = TVar (Map NodeId LastResourcesForNode)
+
+initLastResources :: IO LastResources
+initLastResources = newTVarIO M.empty
+
+-- | For the first update only.
+addNullResources
+ :: LastResources
+ -> NodeId
+ -> IO ()
+addNullResources lastResources nodeId = atomically $
+ modifyTVar' lastResources $ M.insert nodeId nulls
+ where
+ nulls =
+ LastResourcesForNode
+ { cpuLastTicks = 0
+ , cpuLastNS = 0
+ }
+
+updateLastResources
+ :: LastResources
+ -> NodeId
+ -> (LastResourcesForNode -> LastResourcesForNode)
+ -> IO ()
+updateLastResources lastResources nodeId updateIt = atomically $
+ modifyTVar' lastResources $ M.adjust updateIt nodeId
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Peers.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Peers.hs
new file mode 100644
index 00000000000..9687814787f
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Peers.hs
@@ -0,0 +1,73 @@
+module Cardano.Tracer.Handlers.RTView.State.Peers
+ ( PeerAddress
+ , PeersForNode
+ , Peers
+ , addPeer
+ , doesPeerExist
+ , getPeersAddresses
+ , initPeers
+ , removePeer
+ ) where
+
+import Control.Concurrent.STM (atomically)
+import Control.Concurrent.STM.TVar
+import Data.Map.Strict (Map)
+import qualified Data.Map.Strict as M
+import Data.Set (Set)
+import qualified Data.Set as S
+import Data.Text (Text)
+
+import Cardano.Tracer.Types (NodeId)
+
+type PeerAddress = Text
+type PeersForNode = Set PeerAddress
+type Peers = TVar (Map NodeId PeersForNode)
+
+initPeers :: IO Peers
+initPeers = newTVarIO M.empty
+
+addPeer
+ :: Peers
+ -> NodeId
+ -> PeerAddress
+ -> IO ()
+addPeer peers nodeId peerAddr = atomically $
+ modifyTVar' peers $ \currentPeers ->
+ case M.lookup nodeId currentPeers of
+ Nothing ->
+ M.insert nodeId (S.singleton peerAddr) currentPeers
+ Just peersForNode ->
+ M.adjust (const $ S.insert peerAddr peersForNode) nodeId currentPeers
+
+removePeer
+ :: Peers
+ -> NodeId
+ -> PeerAddress
+ -> IO ()
+removePeer peers nodeId peerAddr = atomically $
+ modifyTVar' peers $ \currentPeers ->
+ case M.lookup nodeId currentPeers of
+ Nothing -> currentPeers
+ Just peersForNode ->
+ M.adjust (const $ S.delete peerAddr peersForNode) nodeId currentPeers
+
+doesPeerExist
+ :: Peers
+ -> NodeId
+ -> PeerAddress
+ -> IO Bool
+doesPeerExist peers nodeId peerAddr = do
+ peers' <- readTVarIO peers
+ case M.lookup nodeId peers' of
+ Nothing -> return False
+ Just peersForNode -> return $ S.member peerAddr peersForNode
+
+getPeersAddresses
+ :: Peers
+ -> NodeId
+ -> IO (Set PeerAddress)
+getPeersAddresses peers nodeId = do
+ peers' <- readTVarIO peers
+ case M.lookup nodeId peers' of
+ Nothing -> return S.empty
+ Just peersForNode -> return peersForNode
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/TraceObjects.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/TraceObjects.hs
new file mode 100644
index 00000000000..eaf352defed
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/TraceObjects.hs
@@ -0,0 +1,62 @@
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Cardano.Tracer.Handlers.RTView.State.TraceObjects
+ ( Namespace
+ , SavedTraceObjects
+ , TraceObjectInfo
+ , initSavedTraceObjects
+ , saveTraceObjects
+ ) where
+
+import Control.Concurrent.STM (atomically)
+import Control.Concurrent.STM.TVar (TVar, modifyTVar', newTVarIO)
+import Control.Monad (unless)
+import Data.Map.Strict (Map)
+import qualified Data.Map.Strict as M
+import Data.Maybe (mapMaybe)
+import Data.Text (Text, intercalate)
+import Data.Time.Clock (UTCTime)
+
+import Cardano.Logging (TraceObject (..), SeverityS)
+
+import Cardano.Tracer.Types (NodeId)
+
+type Namespace = Text
+type TraceObjectInfo = (Text, SeverityS, UTCTime)
+
+-- | We have to store 'TraceObject's received from the node,
+-- to be able to update corresponding elements (on the web page)
+-- using the values extracted from these 'TraceObject's.
+type SavedForNode = Map Namespace TraceObjectInfo
+type SavedTraceObjects = TVar (Map NodeId SavedForNode)
+
+initSavedTraceObjects :: IO SavedTraceObjects
+initSavedTraceObjects = newTVarIO M.empty
+
+saveTraceObjects :: SavedTraceObjects -> NodeId -> [TraceObject] -> IO ()
+saveTraceObjects savedTraceObjects nodeId traceObjects =
+ unless (null itemsToSave) $ atomically $ modifyTVar' savedTraceObjects $ \savedTO ->
+ case M.lookup nodeId savedTO of
+ Nothing ->
+ M.insert nodeId (M.fromList itemsToSave) savedTO
+ Just savedTOForThisNode ->
+ M.adjust (const $ savedTOForThisNode `updateSavedBy` itemsToSave) nodeId savedTO
+ where
+ itemsToSave = mapMaybe getTOValue traceObjects
+
+ getTOValue TraceObject{toNamespace, toHuman, toMachine, toSeverity, toTimestamp} =
+ case (toNamespace, toHuman, toMachine) of
+ ([], _, _) -> Nothing
+ (ns, Just msg, Nothing) -> Just (mkName ns, (msg, toSeverity, toTimestamp))
+ (ns, Nothing, Just msg) -> Just (mkName ns, (msg, toSeverity, toTimestamp))
+ (ns, Just msg, Just _) -> Just (mkName ns, (msg, toSeverity, toTimestamp))
+ _ -> Nothing
+
+ mkName = intercalate "."
+
+ -- Update saved 'TraceObject's by new ones: existing value will be replaced.
+ updateSavedBy = go
+ where
+ go saved [] = saved
+ go saved ((ns, toI):others) = M.insert ns toI saved `go` others
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/System.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/System.hs
new file mode 100644
index 00000000000..7fcb471923e
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/System.hs
@@ -0,0 +1,39 @@
+{-# LANGUAGE CPP #-}
+
+module Cardano.Tracer.Handlers.RTView.System
+ ( getPathToChartsConfig
+ , getPathToThemeConfig
+ , getProcessId
+ ) where
+
+import Data.Word (Word32)
+import Graphics.UI.Threepenny.Core
+import System.Directory
+import System.FilePath ((>))
+
+#if defined(mingw32_HOST_OS)
+import System.Win32.Process (getCurrentProcessId)
+#else
+import System.Posix.Process (getProcessID)
+import System.Posix.Types (CPid (..))
+#endif
+
+getProcessId :: UI Word32
+getProcessId =
+#if defined(mingw32_HOST_OS)
+ liftIO getCurrentProcessId
+#else
+ do CPid pid <- liftIO getProcessID
+ return $ fromIntegral pid
+#endif
+
+getPathToChartsConfig, getPathToThemeConfig :: IO FilePath
+getPathToChartsConfig = getPathToConfig "charts"
+getPathToThemeConfig = getPathToConfig "theme"
+
+getPathToConfig :: FilePath -> IO FilePath
+getPathToConfig configName = do
+ configDir <- getXdgDirectory XdgConfig ""
+ let pathToRTViewConfigDir = configDir > "cardano-rt-view"
+ createDirectoryIfMissing True pathToRTViewConfigDir
+ return $ pathToRTViewConfigDir > configName
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/CSS/Bulma.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/CSS/Bulma.hs
new file mode 100644
index 00000000000..c2ee553f2c7
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/CSS/Bulma.hs
@@ -0,0 +1,40 @@
+{-# LANGUAGE QuasiQuotes #-}
+
+module Cardano.Tracer.Handlers.RTView.UI.CSS.Bulma
+ ( bulmaCSS
+ , bulmaTooltipCSS
+ , bulmaPageloaderCSS
+ , bulmaSwitchCSS
+ , bulmaDividerCSS
+ ) where
+
+import Data.String.QQ
+
+-- | To avoid run-time dependency from the static content, embed Bulma in the page's header.
+bulmaCSS :: String
+bulmaCSS = [s|
+/*! bulma.io v0.9.3 | MIT License | github.com/jgthms/bulma */.button,.file-cta,.file-name,.input,.pagination-ellipsis,.pagination-link,.pagination-next,.pagination-previous,.select select,.textarea{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:4px;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(.5em - 1px);padding-left:calc(.75em - 1px);padding-right:calc(.75em - 1px);padding-top:calc(.5em - 1px);position:relative;vertical-align:top}.button:active,.button:focus,.file-cta:active,.file-cta:focus,.file-name:active,.file-name:focus,.input:active,.input:focus,.is-active.button,.is-active.file-cta,.is-active.file-name,.is-active.input,.is-active.pagination-ellipsis,.is-active.pagination-link,.is-active.pagination-next,.is-active.pagination-previous,.is-active.textarea,.is-focused.button,.is-focused.file-cta,.is-focused.file-name,.is-focused.input,.is-focused.pagination-ellipsis,.is-focused.pagination-link,.is-focused.pagination-next,.is-focused.pagination-previous,.is-focused.textarea,.pagination-ellipsis:active,.pagination-ellipsis:focus,.pagination-link:active,.pagination-link:focus,.pagination-next:active,.pagination-next:focus,.pagination-previous:active,.pagination-previous:focus,.select select.is-active,.select select.is-focused,.select select:active,.select select:focus,.textarea:active,.textarea:focus{outline:0}.button[disabled],.file-cta[disabled],.file-name[disabled],.input[disabled],.pagination-ellipsis[disabled],.pagination-link[disabled],.pagination-next[disabled],.pagination-previous[disabled],.select fieldset[disabled] select,.select select[disabled],.textarea[disabled],fieldset[disabled] .button,fieldset[disabled] .file-cta,fieldset[disabled] .file-name,fieldset[disabled] .input,fieldset[disabled] .pagination-ellipsis,fieldset[disabled] .pagination-link,fieldset[disabled] .pagination-next,fieldset[disabled] .pagination-previous,fieldset[disabled] .select select,fieldset[disabled] .textarea{cursor:not-allowed}.breadcrumb,.button,.file,.is-unselectable,.pagination-ellipsis,.pagination-link,.pagination-next,.pagination-previous,.tabs{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.navbar-link:not(.is-arrowless)::after,.select:not(.is-multiple):not(.is-loading)::after{border:3px solid transparent;border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:.625em;margin-top:-.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:.625em}.block:not(:last-child),.box:not(:last-child),.breadcrumb:not(:last-child),.content:not(:last-child),.level:not(:last-child),.message:not(:last-child),.notification:not(:last-child),.pagination:not(:last-child),.progress:not(:last-child),.subtitle:not(:last-child),.table-container:not(:last-child),.table:not(:last-child),.tabs:not(:last-child),.title:not(:last-child){margin-bottom:1.5rem}.delete,.modal-close{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:0;position:relative;vertical-align:top;width:20px}.delete::after,.delete::before,.modal-close::after,.modal-close::before{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.delete::before,.modal-close::before{height:2px;width:50%}.delete::after,.modal-close::after{height:50%;width:2px}.delete:focus,.delete:hover,.modal-close:focus,.modal-close:hover{background-color:rgba(10,10,10,.3)}.delete:active,.modal-close:active{background-color:rgba(10,10,10,.4)}.is-small.delete,.is-small.modal-close{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}.is-medium.delete,.is-medium.modal-close{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}.is-large.delete,.is-large.modal-close{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}.button.is-loading::after,.control.is-loading::after,.loader,.select.is-loading::after{-webkit-animation:spinAround .5s infinite linear;animation:spinAround .5s infinite linear;border:2px solid #dbdbdb;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}.hero-video,.image.is-16by9 .has-ratio,.image.is-16by9 img,.image.is-1by1 .has-ratio,.image.is-1by1 img,.image.is-1by2 .has-ratio,.image.is-1by2 img,.image.is-1by3 .has-ratio,.image.is-1by3 img,.image.is-2by1 .has-ratio,.image.is-2by1 img,.image.is-2by3 .has-ratio,.image.is-2by3 img,.image.is-3by1 .has-ratio,.image.is-3by1 img,.image.is-3by2 .has-ratio,.image.is-3by2 img,.image.is-3by4 .has-ratio,.image.is-3by4 img,.image.is-3by5 .has-ratio,.image.is-3by5 img,.image.is-4by3 .has-ratio,.image.is-4by3 img,.image.is-4by5 .has-ratio,.image.is-4by5 img,.image.is-5by3 .has-ratio,.image.is-5by3 img,.image.is-5by4 .has-ratio,.image.is-5by4 img,.image.is-9by16 .has-ratio,.image.is-9by16 img,.image.is-square .has-ratio,.image.is-square img,.is-overlay,.modal,.modal-background{bottom:0;left:0;position:absolute;right:0;top:0}.navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:0 0;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */blockquote,body,dd,dl,dt,fieldset,figure,h1,h2,h3,h4,h5,h6,hr,html,iframe,legend,li,ol,p,pre,textarea,ul{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:400}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,::after,::before{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}html{background-color:#fff;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:hidden;overflow-y:scroll;text-rendering:optimizeLegibility;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%}article,aside,figure,footer,header,hgroup,section{display:block}body,button,input,optgroup,select,textarea{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif}code,pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:monospace}body{color:#4a4a4a;font-size:1em;font-weight:400;line-height:1.5}a{color:#485fc7;cursor:pointer;text-decoration:none}a strong{color:currentColor}a:hover{color:#363636}code{background-color:#f5f5f5;color:#da1039;font-size:.875em;font-weight:400;padding:.25em .5em .25em}hr{background-color:#f5f5f5;border:none;display:block;height:2px;margin:1.5rem 0}img{height:auto;max-width:100%}input[type=checkbox],input[type=radio]{vertical-align:baseline}small{font-size:.875em}span{font-style:inherit;font-weight:inherit}strong{color:#363636;font-weight:700}fieldset{border:none}pre{-webkit-overflow-scrolling:touch;background-color:#f5f5f5;color:#4a4a4a;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}table td,table th{vertical-align:top}table td:not([align]),table th:not([align]){text-align:inherit}table th{color:#363636}@-webkit-keyframes spinAround{from{transform:rotate(0)}to{transform:rotate(359deg)}}@keyframes spinAround{from{transform:rotate(0)}to{transform:rotate(359deg)}}.box{background-color:#fff;border-radius:6px;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);color:#4a4a4a;display:block;padding:1.25rem}a.box:focus,a.box:hover{box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px #485fc7}a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,.2),0 0 0 1px #485fc7}.button{background-color:#fff;border-color:#dbdbdb;border-width:1px;color:#363636;cursor:pointer;justify-content:center;padding-bottom:calc(.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(.5em - 1px);text-align:center;white-space:nowrap}.button strong{color:inherit}.button .icon,.button .icon.is-large,.button .icon.is-medium,.button .icon.is-small{height:1.5em;width:1.5em}.button .icon:first-child:not(:last-child){margin-left:calc(-.5em - 1px);margin-right:.25em}.button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-.5em - 1px)}.button .icon:first-child:last-child{margin-left:calc(-.5em - 1px);margin-right:calc(-.5em - 1px)}.button.is-hovered,.button:hover{border-color:#b5b5b5;color:#363636}.button.is-focused,.button:focus{border-color:#485fc7;color:#363636}.button.is-focused:not(:active),.button:focus:not(:active){box-shadow:0 0 0 .125em rgba(72,95,199,.25)}.button.is-active,.button:active{border-color:#4a4a4a;color:#363636}.button.is-text{background-color:transparent;border-color:transparent;color:#4a4a4a;text-decoration:underline}.button.is-text.is-focused,.button.is-text.is-hovered,.button.is-text:focus,.button.is-text:hover{background-color:#f5f5f5;color:#363636}.button.is-text.is-active,.button.is-text:active{background-color:#e8e8e8;color:#363636}.button.is-text[disabled],fieldset[disabled] .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}.button.is-ghost{background:0 0;border-color:transparent;color:#485fc7;text-decoration:none}.button.is-ghost.is-hovered,.button.is-ghost:hover{color:#485fc7;text-decoration:underline}.button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}.button.is-white.is-hovered,.button.is-white:hover{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.button.is-white.is-focused,.button.is-white:focus{border-color:transparent;color:#0a0a0a}.button.is-white.is-focused:not(:active),.button.is-white:focus:not(:active){box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.button.is-white.is-active,.button.is-white:active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.button.is-white[disabled],fieldset[disabled] .button.is-white{background-color:#fff;border-color:transparent;box-shadow:none}.button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted.is-hovered,.button.is-white.is-inverted:hover{background-color:#000}.button.is-white.is-inverted[disabled],fieldset[disabled] .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}.button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-white.is-outlined.is-focused,.button.is-white.is-outlined.is-hovered,.button.is-white.is-outlined:focus,.button.is-white.is-outlined:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}.button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-white.is-outlined.is-loading.is-focused::after,.button.is-white.is-outlined.is-loading.is-hovered::after,.button.is-white.is-outlined.is-loading:focus::after,.button.is-white.is-outlined.is-loading:hover::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-white.is-outlined[disabled],fieldset[disabled] .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-white.is-inverted.is-outlined.is-focused,.button.is-white.is-inverted.is-outlined.is-hovered,.button.is-white.is-inverted.is-outlined:focus,.button.is-white.is-inverted.is-outlined:hover{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-white.is-inverted.is-outlined.is-loading:focus::after,.button.is-white.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}.button.is-black.is-hovered,.button.is-black:hover{background-color:#040404;border-color:transparent;color:#fff}.button.is-black.is-focused,.button.is-black:focus{border-color:transparent;color:#fff}.button.is-black.is-focused:not(:active),.button.is-black:focus:not(:active){box-shadow:0 0 0 .125em rgba(10,10,10,.25)}.button.is-black.is-active,.button.is-black:active{background-color:#000;border-color:transparent;color:#fff}.button.is-black[disabled],fieldset[disabled] .button.is-black{background-color:#0a0a0a;border-color:transparent;box-shadow:none}.button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted.is-hovered,.button.is-black.is-inverted:hover{background-color:#f2f2f2}.button.is-black.is-inverted[disabled],fieldset[disabled] .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}.button.is-black.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-black.is-outlined.is-focused,.button.is-black.is-outlined.is-hovered,.button.is-black.is-outlined:focus,.button.is-black.is-outlined:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-black.is-outlined.is-loading.is-focused::after,.button.is-black.is-outlined.is-loading.is-hovered::after,.button.is-black.is-outlined.is-loading:focus::after,.button.is-black.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-black.is-outlined[disabled],fieldset[disabled] .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-black.is-inverted.is-outlined.is-focused,.button.is-black.is-inverted.is-outlined.is-hovered,.button.is-black.is-inverted.is-outlined:focus,.button.is-black.is-inverted.is-outlined:hover{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-black.is-inverted.is-outlined.is-loading:focus::after,.button.is-black.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-light{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light.is-hovered,.button.is-light:hover{background-color:#eee;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light.is-focused,.button.is-light:focus{border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light.is-focused:not(:active),.button.is-light:focus:not(:active){box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.button.is-light.is-active,.button.is-light:active{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light[disabled],fieldset[disabled] .button.is-light{background-color:#f5f5f5;border-color:transparent;box-shadow:none}.button.is-light.is-inverted{background-color:rgba(0,0,0,.7);color:#f5f5f5}.button.is-light.is-inverted.is-hovered,.button.is-light.is-inverted:hover{background-color:rgba(0,0,0,.7)}.button.is-light.is-inverted[disabled],fieldset[disabled] .button.is-light.is-inverted{background-color:rgba(0,0,0,.7);border-color:transparent;box-shadow:none;color:#f5f5f5}.button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}.button.is-light.is-outlined.is-focused,.button.is-light.is-outlined.is-hovered,.button.is-light.is-outlined:focus,.button.is-light.is-outlined:hover{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,.7)}.button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5!important}.button.is-light.is-outlined.is-loading.is-focused::after,.button.is-light.is-outlined.is-loading.is-hovered::after,.button.is-light.is-outlined.is-loading:focus::after,.button.is-light.is-outlined.is-loading:hover::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-light.is-outlined[disabled],fieldset[disabled] .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}.button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);color:rgba(0,0,0,.7)}.button.is-light.is-inverted.is-outlined.is-focused,.button.is-light.is-inverted.is-outlined.is-hovered,.button.is-light.is-inverted.is-outlined:focus,.button.is-light.is-inverted.is-outlined:hover{background-color:rgba(0,0,0,.7);color:#f5f5f5}.button.is-light.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-light.is-inverted.is-outlined.is-loading:focus::after,.button.is-light.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #f5f5f5 #f5f5f5!important}.button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);box-shadow:none;color:rgba(0,0,0,.7)}.button.is-dark{background-color:#363636;border-color:transparent;color:#fff}.button.is-dark.is-hovered,.button.is-dark:hover{background-color:#2f2f2f;border-color:transparent;color:#fff}.button.is-dark.is-focused,.button.is-dark:focus{border-color:transparent;color:#fff}.button.is-dark.is-focused:not(:active),.button.is-dark:focus:not(:active){box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.button.is-dark.is-active,.button.is-dark:active{background-color:#292929;border-color:transparent;color:#fff}.button.is-dark[disabled],fieldset[disabled] .button.is-dark{background-color:#363636;border-color:transparent;box-shadow:none}.button.is-dark.is-inverted{background-color:#fff;color:#363636}.button.is-dark.is-inverted.is-hovered,.button.is-dark.is-inverted:hover{background-color:#f2f2f2}.button.is-dark.is-inverted[disabled],fieldset[disabled] .button.is-dark.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#363636}.button.is-dark.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-dark.is-outlined{background-color:transparent;border-color:#363636;color:#363636}.button.is-dark.is-outlined.is-focused,.button.is-dark.is-outlined.is-hovered,.button.is-dark.is-outlined:focus,.button.is-dark.is-outlined:hover{background-color:#363636;border-color:#363636;color:#fff}.button.is-dark.is-outlined.is-loading::after{border-color:transparent transparent #363636 #363636!important}.button.is-dark.is-outlined.is-loading.is-focused::after,.button.is-dark.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-outlined.is-loading:focus::after,.button.is-dark.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-dark.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-outlined{background-color:transparent;border-color:#363636;box-shadow:none;color:#363636}.button.is-dark.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-dark.is-inverted.is-outlined.is-focused,.button.is-dark.is-inverted.is-outlined.is-hovered,.button.is-dark.is-inverted.is-outlined:focus,.button.is-dark.is-inverted.is-outlined:hover{background-color:#fff;color:#363636}.button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-inverted.is-outlined.is-loading:focus::after,.button.is-dark.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #363636 #363636!important}.button.is-dark.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary{background-color:#00d1b2;border-color:transparent;color:#fff}.button.is-primary.is-hovered,.button.is-primary:hover{background-color:#00c4a7;border-color:transparent;color:#fff}.button.is-primary.is-focused,.button.is-primary:focus{border-color:transparent;color:#fff}.button.is-primary.is-focused:not(:active),.button.is-primary:focus:not(:active){box-shadow:0 0 0 .125em rgba(0,209,178,.25)}.button.is-primary.is-active,.button.is-primary:active{background-color:#00b89c;border-color:transparent;color:#fff}.button.is-primary[disabled],fieldset[disabled] .button.is-primary{background-color:#00d1b2;border-color:transparent;box-shadow:none}.button.is-primary.is-inverted{background-color:#fff;color:#00d1b2}.button.is-primary.is-inverted.is-hovered,.button.is-primary.is-inverted:hover{background-color:#f2f2f2}.button.is-primary.is-inverted[disabled],fieldset[disabled] .button.is-primary.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#00d1b2}.button.is-primary.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-primary.is-outlined{background-color:transparent;border-color:#00d1b2;color:#00d1b2}.button.is-primary.is-outlined.is-focused,.button.is-primary.is-outlined.is-hovered,.button.is-primary.is-outlined:focus,.button.is-primary.is-outlined:hover{background-color:#00d1b2;border-color:#00d1b2;color:#fff}.button.is-primary.is-outlined.is-loading::after{border-color:transparent transparent #00d1b2 #00d1b2!important}.button.is-primary.is-outlined.is-loading.is-focused::after,.button.is-primary.is-outlined.is-loading.is-hovered::after,.button.is-primary.is-outlined.is-loading:focus::after,.button.is-primary.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-primary.is-outlined[disabled],fieldset[disabled] .button.is-primary.is-outlined{background-color:transparent;border-color:#00d1b2;box-shadow:none;color:#00d1b2}.button.is-primary.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-primary.is-inverted.is-outlined.is-focused,.button.is-primary.is-inverted.is-outlined.is-hovered,.button.is-primary.is-inverted.is-outlined:focus,.button.is-primary.is-inverted.is-outlined:hover{background-color:#fff;color:#00d1b2}.button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-primary.is-inverted.is-outlined.is-loading:focus::after,.button.is-primary.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #00d1b2 #00d1b2!important}.button.is-primary.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-primary.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary.is-light{background-color:#ebfffc;color:#00947e}.button.is-primary.is-light.is-hovered,.button.is-primary.is-light:hover{background-color:#defffa;border-color:transparent;color:#00947e}.button.is-primary.is-light.is-active,.button.is-primary.is-light:active{background-color:#d1fff8;border-color:transparent;color:#00947e}.button.is-link{background-color:#485fc7;border-color:transparent;color:#fff}.button.is-link.is-hovered,.button.is-link:hover{background-color:#3e56c4;border-color:transparent;color:#fff}.button.is-link.is-focused,.button.is-link:focus{border-color:transparent;color:#fff}.button.is-link.is-focused:not(:active),.button.is-link:focus:not(:active){box-shadow:0 0 0 .125em rgba(72,95,199,.25)}.button.is-link.is-active,.button.is-link:active{background-color:#3a51bb;border-color:transparent;color:#fff}.button.is-link[disabled],fieldset[disabled] .button.is-link{background-color:#485fc7;border-color:transparent;box-shadow:none}.button.is-link.is-inverted{background-color:#fff;color:#485fc7}.button.is-link.is-inverted.is-hovered,.button.is-link.is-inverted:hover{background-color:#f2f2f2}.button.is-link.is-inverted[disabled],fieldset[disabled] .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#485fc7}.button.is-link.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-link.is-outlined{background-color:transparent;border-color:#485fc7;color:#485fc7}.button.is-link.is-outlined.is-focused,.button.is-link.is-outlined.is-hovered,.button.is-link.is-outlined:focus,.button.is-link.is-outlined:hover{background-color:#485fc7;border-color:#485fc7;color:#fff}.button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #485fc7 #485fc7!important}.button.is-link.is-outlined.is-loading.is-focused::after,.button.is-link.is-outlined.is-loading.is-hovered::after,.button.is-link.is-outlined.is-loading:focus::after,.button.is-link.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-link.is-outlined[disabled],fieldset[disabled] .button.is-link.is-outlined{background-color:transparent;border-color:#485fc7;box-shadow:none;color:#485fc7}.button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-link.is-inverted.is-outlined.is-focused,.button.is-link.is-inverted.is-outlined.is-hovered,.button.is-link.is-inverted.is-outlined:focus,.button.is-link.is-inverted.is-outlined:hover{background-color:#fff;color:#485fc7}.button.is-link.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-link.is-inverted.is-outlined.is-loading:focus::after,.button.is-link.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #485fc7 #485fc7!important}.button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-link.is-light{background-color:#eff1fa;color:#3850b7}.button.is-link.is-light.is-hovered,.button.is-link.is-light:hover{background-color:#e6e9f7;border-color:transparent;color:#3850b7}.button.is-link.is-light.is-active,.button.is-link.is-light:active{background-color:#dce0f4;border-color:transparent;color:#3850b7}.button.is-info{background-color:#3e8ed0;border-color:transparent;color:#fff}.button.is-info.is-hovered,.button.is-info:hover{background-color:#3488ce;border-color:transparent;color:#fff}.button.is-info.is-focused,.button.is-info:focus{border-color:transparent;color:#fff}.button.is-info.is-focused:not(:active),.button.is-info:focus:not(:active){box-shadow:0 0 0 .125em rgba(62,142,208,.25)}.button.is-info.is-active,.button.is-info:active{background-color:#3082c5;border-color:transparent;color:#fff}.button.is-info[disabled],fieldset[disabled] .button.is-info{background-color:#3e8ed0;border-color:transparent;box-shadow:none}.button.is-info.is-inverted{background-color:#fff;color:#3e8ed0}.button.is-info.is-inverted.is-hovered,.button.is-info.is-inverted:hover{background-color:#f2f2f2}.button.is-info.is-inverted[disabled],fieldset[disabled] .button.is-info.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#3e8ed0}.button.is-info.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-info.is-outlined{background-color:transparent;border-color:#3e8ed0;color:#3e8ed0}.button.is-info.is-outlined.is-focused,.button.is-info.is-outlined.is-hovered,.button.is-info.is-outlined:focus,.button.is-info.is-outlined:hover{background-color:#3e8ed0;border-color:#3e8ed0;color:#fff}.button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #3e8ed0 #3e8ed0!important}.button.is-info.is-outlined.is-loading.is-focused::after,.button.is-info.is-outlined.is-loading.is-hovered::after,.button.is-info.is-outlined.is-loading:focus::after,.button.is-info.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-info.is-outlined[disabled],fieldset[disabled] .button.is-info.is-outlined{background-color:transparent;border-color:#3e8ed0;box-shadow:none;color:#3e8ed0}.button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-info.is-inverted.is-outlined.is-focused,.button.is-info.is-inverted.is-outlined.is-hovered,.button.is-info.is-inverted.is-outlined:focus,.button.is-info.is-inverted.is-outlined:hover{background-color:#fff;color:#3e8ed0}.button.is-info.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-info.is-inverted.is-outlined.is-loading:focus::after,.button.is-info.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #3e8ed0 #3e8ed0!important}.button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-info.is-light{background-color:#eff5fb;color:#296fa8}.button.is-info.is-light.is-hovered,.button.is-info.is-light:hover{background-color:#e4eff9;border-color:transparent;color:#296fa8}.button.is-info.is-light.is-active,.button.is-info.is-light:active{background-color:#dae9f6;border-color:transparent;color:#296fa8}.button.is-success{background-color:#48c78e;border-color:transparent;color:#fff}.button.is-success.is-hovered,.button.is-success:hover{background-color:#3ec487;border-color:transparent;color:#fff}.button.is-success.is-focused,.button.is-success:focus{border-color:transparent;color:#fff}.button.is-success.is-focused:not(:active),.button.is-success:focus:not(:active){box-shadow:0 0 0 .125em rgba(72,199,142,.25)}.button.is-success.is-active,.button.is-success:active{background-color:#3abb81;border-color:transparent;color:#fff}.button.is-success[disabled],fieldset[disabled] .button.is-success{background-color:#48c78e;border-color:transparent;box-shadow:none}.button.is-success.is-inverted{background-color:#fff;color:#48c78e}.button.is-success.is-inverted.is-hovered,.button.is-success.is-inverted:hover{background-color:#f2f2f2}.button.is-success.is-inverted[disabled],fieldset[disabled] .button.is-success.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#48c78e}.button.is-success.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-success.is-outlined{background-color:transparent;border-color:#48c78e;color:#48c78e}.button.is-success.is-outlined.is-focused,.button.is-success.is-outlined.is-hovered,.button.is-success.is-outlined:focus,.button.is-success.is-outlined:hover{background-color:#48c78e;border-color:#48c78e;color:#fff}.button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #48c78e #48c78e!important}.button.is-success.is-outlined.is-loading.is-focused::after,.button.is-success.is-outlined.is-loading.is-hovered::after,.button.is-success.is-outlined.is-loading:focus::after,.button.is-success.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-success.is-outlined[disabled],fieldset[disabled] .button.is-success.is-outlined{background-color:transparent;border-color:#48c78e;box-shadow:none;color:#48c78e}.button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-success.is-inverted.is-outlined.is-focused,.button.is-success.is-inverted.is-outlined.is-hovered,.button.is-success.is-inverted.is-outlined:focus,.button.is-success.is-inverted.is-outlined:hover{background-color:#fff;color:#48c78e}.button.is-success.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-success.is-inverted.is-outlined.is-loading:focus::after,.button.is-success.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #48c78e #48c78e!important}.button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-success.is-light{background-color:#effaf5;color:#257953}.button.is-success.is-light.is-hovered,.button.is-success.is-light:hover{background-color:#e6f7ef;border-color:transparent;color:#257953}.button.is-success.is-light.is-active,.button.is-success.is-light:active{background-color:#dcf4e9;border-color:transparent;color:#257953}.button.is-warning{background-color:#ffe08a;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning.is-hovered,.button.is-warning:hover{background-color:#ffdc7d;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning.is-focused,.button.is-warning:focus{border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning.is-focused:not(:active),.button.is-warning:focus:not(:active){box-shadow:0 0 0 .125em rgba(255,224,138,.25)}.button.is-warning.is-active,.button.is-warning:active{background-color:#ffd970;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning[disabled],fieldset[disabled] .button.is-warning{background-color:#ffe08a;border-color:transparent;box-shadow:none}.button.is-warning.is-inverted{background-color:rgba(0,0,0,.7);color:#ffe08a}.button.is-warning.is-inverted.is-hovered,.button.is-warning.is-inverted:hover{background-color:rgba(0,0,0,.7)}.button.is-warning.is-inverted[disabled],fieldset[disabled] .button.is-warning.is-inverted{background-color:rgba(0,0,0,.7);border-color:transparent;box-shadow:none;color:#ffe08a}.button.is-warning.is-loading::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-warning.is-outlined{background-color:transparent;border-color:#ffe08a;color:#ffe08a}.button.is-warning.is-outlined.is-focused,.button.is-warning.is-outlined.is-hovered,.button.is-warning.is-outlined:focus,.button.is-warning.is-outlined:hover{background-color:#ffe08a;border-color:#ffe08a;color:rgba(0,0,0,.7)}.button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #ffe08a #ffe08a!important}.button.is-warning.is-outlined.is-loading.is-focused::after,.button.is-warning.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-outlined.is-loading:focus::after,.button.is-warning.is-outlined.is-loading:hover::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-warning.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-outlined{background-color:transparent;border-color:#ffe08a;box-shadow:none;color:#ffe08a}.button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);color:rgba(0,0,0,.7)}.button.is-warning.is-inverted.is-outlined.is-focused,.button.is-warning.is-inverted.is-outlined.is-hovered,.button.is-warning.is-inverted.is-outlined:focus,.button.is-warning.is-inverted.is-outlined:hover{background-color:rgba(0,0,0,.7);color:#ffe08a}.button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-inverted.is-outlined.is-loading:focus::after,.button.is-warning.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #ffe08a #ffe08a!important}.button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);box-shadow:none;color:rgba(0,0,0,.7)}.button.is-warning.is-light{background-color:#fffaeb;color:#946c00}.button.is-warning.is-light.is-hovered,.button.is-warning.is-light:hover{background-color:#fff6de;border-color:transparent;color:#946c00}.button.is-warning.is-light.is-active,.button.is-warning.is-light:active{background-color:#fff3d1;border-color:transparent;color:#946c00}.button.is-danger{background-color:#f14668;border-color:transparent;color:#fff}.button.is-danger.is-hovered,.button.is-danger:hover{background-color:#f03a5f;border-color:transparent;color:#fff}.button.is-danger.is-focused,.button.is-danger:focus{border-color:transparent;color:#fff}.button.is-danger.is-focused:not(:active),.button.is-danger:focus:not(:active){box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.button.is-danger.is-active,.button.is-danger:active{background-color:#ef2e55;border-color:transparent;color:#fff}.button.is-danger[disabled],fieldset[disabled] .button.is-danger{background-color:#f14668;border-color:transparent;box-shadow:none}.button.is-danger.is-inverted{background-color:#fff;color:#f14668}.button.is-danger.is-inverted.is-hovered,.button.is-danger.is-inverted:hover{background-color:#f2f2f2}.button.is-danger.is-inverted[disabled],fieldset[disabled] .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#f14668}.button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-danger.is-outlined{background-color:transparent;border-color:#f14668;color:#f14668}.button.is-danger.is-outlined.is-focused,.button.is-danger.is-outlined.is-hovered,.button.is-danger.is-outlined:focus,.button.is-danger.is-outlined:hover{background-color:#f14668;border-color:#f14668;color:#fff}.button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #f14668 #f14668!important}.button.is-danger.is-outlined.is-loading.is-focused::after,.button.is-danger.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-outlined.is-loading:focus::after,.button.is-danger.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-danger.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-outlined{background-color:transparent;border-color:#f14668;box-shadow:none;color:#f14668}.button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-danger.is-inverted.is-outlined.is-focused,.button.is-danger.is-inverted.is-outlined.is-hovered,.button.is-danger.is-inverted.is-outlined:focus,.button.is-danger.is-inverted.is-outlined:hover{background-color:#fff;color:#f14668}.button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-inverted.is-outlined.is-loading:focus::after,.button.is-danger.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #f14668 #f14668!important}.button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-danger.is-light{background-color:#feecf0;color:#cc0f35}.button.is-danger.is-light.is-hovered,.button.is-danger.is-light:hover{background-color:#fde0e6;border-color:transparent;color:#cc0f35}.button.is-danger.is-light.is-active,.button.is-danger.is-light:active{background-color:#fcd4dc;border-color:transparent;color:#cc0f35}.button.is-small{font-size:.75rem}.button.is-small:not(.is-rounded){border-radius:2px}.button.is-normal{font-size:1rem}.button.is-medium{font-size:1.25rem}.button.is-large{font-size:1.5rem}.button[disabled],fieldset[disabled] .button{background-color:#fff;border-color:#dbdbdb;box-shadow:none;opacity:.5}.button.is-fullwidth{display:flex;width:100%}.button.is-loading{color:transparent!important;pointer-events:none}.button.is-loading::after{position:absolute;left:calc(50% - (1em * .5));top:calc(50% - (1em * .5));position:absolute!important}.button.is-static{background-color:#f5f5f5;border-color:#dbdbdb;color:#7a7a7a;box-shadow:none;pointer-events:none}.button.is-rounded{border-radius:9999px;padding-left:calc(1em + .25em);padding-right:calc(1em + .25em)}.buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.buttons .button{margin-bottom:.5rem}.buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}.buttons:last-child{margin-bottom:-.5rem}.buttons:not(:last-child){margin-bottom:1rem}.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:2px}.buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}.buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}.buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}.buttons.has-addons .button:last-child{margin-right:0}.buttons.has-addons .button.is-hovered,.buttons.has-addons .button:hover{z-index:2}.buttons.has-addons .button.is-active,.buttons.has-addons .button.is-focused,.buttons.has-addons .button.is-selected,.buttons.has-addons .button:active,.buttons.has-addons .button:focus{z-index:3}.buttons.has-addons .button.is-active:hover,.buttons.has-addons .button.is-focused:hover,.buttons.has-addons .button.is-selected:hover,.buttons.has-addons .button:active:hover,.buttons.has-addons .button:focus:hover{z-index:4}.buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}.buttons.is-centered{justify-content:center}.buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:.25rem;margin-right:.25rem}.buttons.is-right{justify-content:flex-end}.buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:.25rem;margin-right:.25rem}.container{flex-grow:1;margin:0 auto;position:relative;width:auto}.container.is-fluid{max-width:none!important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width:1024px){.container{max-width:960px}}@media screen and (max-width:1215px){.container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width:1407px){.container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width:1216px){.container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width:1408px){.container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}.content li+li{margin-top:.25em}.content blockquote:not(:last-child),.content dl:not(:last-child),.content ol:not(:last-child),.content p:not(:last-child),.content pre:not(:last-child),.content table:not(:last-child),.content ul:not(:last-child){margin-bottom:1em}.content h1,.content h2,.content h3,.content h4,.content h5,.content h6{color:#363636;font-weight:600;line-height:1.125}.content h1{font-size:2em;margin-bottom:.5em}.content h1:not(:first-child){margin-top:1em}.content h2{font-size:1.75em;margin-bottom:.5714em}.content h2:not(:first-child){margin-top:1.1428em}.content h3{font-size:1.5em;margin-bottom:.6666em}.content h3:not(:first-child){margin-top:1.3333em}.content h4{font-size:1.25em;margin-bottom:.8em}.content h5{font-size:1.125em;margin-bottom:.8888em}.content h6{font-size:1em;margin-bottom:1em}.content blockquote{background-color:#f5f5f5;border-left:5px solid #dbdbdb;padding:1.25em 1.5em}.content ol{list-style-position:outside;margin-left:2em;margin-top:1em}.content ol:not([type]){list-style-type:decimal}.content ol:not([type]).is-lower-alpha{list-style-type:lower-alpha}.content ol:not([type]).is-lower-roman{list-style-type:lower-roman}.content ol:not([type]).is-upper-alpha{list-style-type:upper-alpha}.content ol:not([type]).is-upper-roman{list-style-type:upper-roman}.content ul{list-style:disc outside;margin-left:2em;margin-top:1em}.content ul ul{list-style-type:circle;margin-top:.5em}.content ul ul ul{list-style-type:square}.content dd{margin-left:2em}.content figure{margin-left:2em;margin-right:2em;text-align:center}.content figure:not(:first-child){margin-top:2em}.content figure:not(:last-child){margin-bottom:2em}.content figure img{display:inline-block}.content figure figcaption{font-style:italic}.content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:1.25em 1.5em;white-space:pre;word-wrap:normal}.content sub,.content sup{font-size:75%}.content table{width:100%}.content table td,.content table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:.5em .75em;vertical-align:top}.content table th{color:#363636}.content table th:not([align]){text-align:inherit}.content table thead td,.content table thead th{border-width:0 0 2px;color:#363636}.content table tfoot td,.content table tfoot th{border-width:2px 0 0;color:#363636}.content table tbody tr:last-child td,.content table tbody tr:last-child th{border-bottom-width:0}.content .tabs li+li{margin-top:0}.content.is-small{font-size:.75rem}.content.is-normal{font-size:1rem}.content.is-medium{font-size:1.25rem}.content.is-large{font-size:1.5rem}.icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}.icon.is-small{height:1rem;width:1rem}.icon.is-medium{height:2rem;width:2rem}.icon.is-large{height:3rem;width:3rem}.icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}.icon-text .icon{flex-grow:0;flex-shrink:0}.icon-text .icon:not(:last-child){margin-right:.25em}.icon-text .icon:not(:first-child){margin-left:.25em}div.icon-text{display:flex}.image{display:block;position:relative}.image img{display:block;height:auto;width:100%}.image img.is-rounded{border-radius:9999px}.image.is-fullwidth{width:100%}.image.is-16by9 .has-ratio,.image.is-16by9 img,.image.is-1by1 .has-ratio,.image.is-1by1 img,.image.is-1by2 .has-ratio,.image.is-1by2 img,.image.is-1by3 .has-ratio,.image.is-1by3 img,.image.is-2by1 .has-ratio,.image.is-2by1 img,.image.is-2by3 .has-ratio,.image.is-2by3 img,.image.is-3by1 .has-ratio,.image.is-3by1 img,.image.is-3by2 .has-ratio,.image.is-3by2 img,.image.is-3by4 .has-ratio,.image.is-3by4 img,.image.is-3by5 .has-ratio,.image.is-3by5 img,.image.is-4by3 .has-ratio,.image.is-4by3 img,.image.is-4by5 .has-ratio,.image.is-4by5 img,.image.is-5by3 .has-ratio,.image.is-5by3 img,.image.is-5by4 .has-ratio,.image.is-5by4 img,.image.is-9by16 .has-ratio,.image.is-9by16 img,.image.is-square .has-ratio,.image.is-square img{height:100%;width:100%}.image.is-1by1,.image.is-square{padding-top:100%}.image.is-5by4{padding-top:80%}.image.is-4by3{padding-top:75%}.image.is-3by2{padding-top:66.6666%}.image.is-5by3{padding-top:60%}.image.is-16by9{padding-top:56.25%}.image.is-2by1{padding-top:50%}.image.is-3by1{padding-top:33.3333%}.image.is-4by5{padding-top:125%}.image.is-3by4{padding-top:133.3333%}.image.is-2by3{padding-top:150%}.image.is-3by5{padding-top:166.6666%}.image.is-9by16{padding-top:177.7777%}.image.is-1by2{padding-top:200%}.image.is-1by3{padding-top:300%}.image.is-16x16{height:16px;width:16px}.image.is-24x24{height:24px;width:24px}.image.is-32x32{height:32px;width:32px}.image.is-48x48{height:48px;width:48px}.image.is-64x64{height:64px;width:64px}.image.is-96x96{height:96px;width:96px}.image.is-128x128{height:128px;width:128px}.notification{background-color:#f5f5f5;border-radius:4px;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}.notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}.notification strong{color:currentColor}.notification code,.notification pre{background:#fff}.notification pre code{background:0 0}.notification>.delete{right:.5rem;position:absolute;top:.5rem}.notification .content,.notification .subtitle,.notification .title{color:currentColor}.notification.is-white{background-color:#fff;color:#0a0a0a}.notification.is-black{background-color:#0a0a0a;color:#fff}.notification.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.notification.is-dark{background-color:#363636;color:#fff}.notification.is-primary{background-color:#00d1b2;color:#fff}.notification.is-primary.is-light{background-color:#ebfffc;color:#00947e}.notification.is-link{background-color:#485fc7;color:#fff}.notification.is-link.is-light{background-color:#eff1fa;color:#3850b7}.notification.is-info{background-color:#3e8ed0;color:#fff}.notification.is-info.is-light{background-color:#eff5fb;color:#296fa8}.notification.is-success{background-color:#48c78e;color:#fff}.notification.is-success.is-light{background-color:#effaf5;color:#257953}.notification.is-warning{background-color:#ffe08a;color:rgba(0,0,0,.7)}.notification.is-warning.is-light{background-color:#fffaeb;color:#946c00}.notification.is-danger{background-color:#f14668;color:#fff}.notification.is-danger.is-light{background-color:#feecf0;color:#cc0f35}.progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}.progress::-webkit-progress-bar{background-color:#ededed}.progress::-webkit-progress-value{background-color:#4a4a4a}.progress::-moz-progress-bar{background-color:#4a4a4a}.progress::-ms-fill{background-color:#4a4a4a;border:none}.progress.is-white::-webkit-progress-value{background-color:#fff}.progress.is-white::-moz-progress-bar{background-color:#fff}.progress.is-white::-ms-fill{background-color:#fff}.progress.is-white:indeterminate{background-image:linear-gradient(to right,#fff 30%,#ededed 30%)}.progress.is-black::-webkit-progress-value{background-color:#0a0a0a}.progress.is-black::-moz-progress-bar{background-color:#0a0a0a}.progress.is-black::-ms-fill{background-color:#0a0a0a}.progress.is-black:indeterminate{background-image:linear-gradient(to right,#0a0a0a 30%,#ededed 30%)}.progress.is-light::-webkit-progress-value{background-color:#f5f5f5}.progress.is-light::-moz-progress-bar{background-color:#f5f5f5}.progress.is-light::-ms-fill{background-color:#f5f5f5}.progress.is-light:indeterminate{background-image:linear-gradient(to right,#f5f5f5 30%,#ededed 30%)}.progress.is-dark::-webkit-progress-value{background-color:#363636}.progress.is-dark::-moz-progress-bar{background-color:#363636}.progress.is-dark::-ms-fill{background-color:#363636}.progress.is-dark:indeterminate{background-image:linear-gradient(to right,#363636 30%,#ededed 30%)}.progress.is-primary::-webkit-progress-value{background-color:#00d1b2}.progress.is-primary::-moz-progress-bar{background-color:#00d1b2}.progress.is-primary::-ms-fill{background-color:#00d1b2}.progress.is-primary:indeterminate{background-image:linear-gradient(to right,#00d1b2 30%,#ededed 30%)}.progress.is-link::-webkit-progress-value{background-color:#485fc7}.progress.is-link::-moz-progress-bar{background-color:#485fc7}.progress.is-link::-ms-fill{background-color:#485fc7}.progress.is-link:indeterminate{background-image:linear-gradient(to right,#485fc7 30%,#ededed 30%)}.progress.is-info::-webkit-progress-value{background-color:#3e8ed0}.progress.is-info::-moz-progress-bar{background-color:#3e8ed0}.progress.is-info::-ms-fill{background-color:#3e8ed0}.progress.is-info:indeterminate{background-image:linear-gradient(to right,#3e8ed0 30%,#ededed 30%)}.progress.is-success::-webkit-progress-value{background-color:#48c78e}.progress.is-success::-moz-progress-bar{background-color:#48c78e}.progress.is-success::-ms-fill{background-color:#48c78e}.progress.is-success:indeterminate{background-image:linear-gradient(to right,#48c78e 30%,#ededed 30%)}.progress.is-warning::-webkit-progress-value{background-color:#ffe08a}.progress.is-warning::-moz-progress-bar{background-color:#ffe08a}.progress.is-warning::-ms-fill{background-color:#ffe08a}.progress.is-warning:indeterminate{background-image:linear-gradient(to right,#ffe08a 30%,#ededed 30%)}.progress.is-danger::-webkit-progress-value{background-color:#f14668}.progress.is-danger::-moz-progress-bar{background-color:#f14668}.progress.is-danger::-ms-fill{background-color:#f14668}.progress.is-danger:indeterminate{background-image:linear-gradient(to right,#f14668 30%,#ededed 30%)}.progress:indeterminate{-webkit-animation-duration:1.5s;animation-duration:1.5s;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-name:moveIndeterminate;animation-name:moveIndeterminate;-webkit-animation-timing-function:linear;animation-timing-function:linear;background-color:#ededed;background-image:linear-gradient(to right,#4a4a4a 30%,#ededed 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}.progress:indeterminate::-webkit-progress-bar{background-color:transparent}.progress:indeterminate::-moz-progress-bar{background-color:transparent}.progress:indeterminate::-ms-fill{animation-name:none}.progress.is-small{height:.75rem}.progress.is-medium{height:1.25rem}.progress.is-large{height:1.5rem}@-webkit-keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}.table{background-color:#fff;color:#363636}.table td,.table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:.5em .75em;vertical-align:top}.table td.is-white,.table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}.table td.is-black,.table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.table td.is-light,.table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,.7)}.table td.is-dark,.table th.is-dark{background-color:#363636;border-color:#363636;color:#fff}.table td.is-primary,.table th.is-primary{background-color:#00d1b2;border-color:#00d1b2;color:#fff}.table td.is-link,.table th.is-link{background-color:#485fc7;border-color:#485fc7;color:#fff}.table td.is-info,.table th.is-info{background-color:#3e8ed0;border-color:#3e8ed0;color:#fff}.table td.is-success,.table th.is-success{background-color:#48c78e;border-color:#48c78e;color:#fff}.table td.is-warning,.table th.is-warning{background-color:#ffe08a;border-color:#ffe08a;color:rgba(0,0,0,.7)}.table td.is-danger,.table th.is-danger{background-color:#f14668;border-color:#f14668;color:#fff}.table td.is-narrow,.table th.is-narrow{white-space:nowrap;width:1%}.table td.is-selected,.table th.is-selected{background-color:#00d1b2;color:#fff}.table td.is-selected a,.table td.is-selected strong,.table th.is-selected a,.table th.is-selected strong{color:currentColor}.table td.is-vcentered,.table th.is-vcentered{vertical-align:middle}.table th{color:#363636}.table th:not([align]){text-align:inherit}.table tr.is-selected{background-color:#00d1b2;color:#fff}.table tr.is-selected a,.table tr.is-selected strong{color:currentColor}.table tr.is-selected td,.table tr.is-selected th{border-color:#fff;color:currentColor}.table thead{background-color:transparent}.table thead td,.table thead th{border-width:0 0 2px;color:#363636}.table tfoot{background-color:transparent}.table tfoot td,.table tfoot th{border-width:2px 0 0;color:#363636}.table tbody{background-color:transparent}.table tbody tr:last-child td,.table tbody tr:last-child th{border-bottom-width:0}.table.is-bordered td,.table.is-bordered th{border-width:1px}.table.is-bordered tr:last-child td,.table.is-bordered tr:last-child th{border-bottom-width:1px}.table.is-fullwidth{width:100%}.table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#f5f5f5}.table.is-narrow td,.table.is-narrow th{padding:.25em .5em}.table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#fafafa}.table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}.tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.tags .tag{margin-bottom:.5rem}.tags .tag:not(:last-child){margin-right:.5rem}.tags:last-child{margin-bottom:-.5rem}.tags:not(:last-child){margin-bottom:1rem}.tags.are-medium .tag:not(.is-normal):not(.is-large){font-size:1rem}.tags.are-large .tag:not(.is-normal):not(.is-medium){font-size:1.25rem}.tags.is-centered{justify-content:center}.tags.is-centered .tag{margin-right:.25rem;margin-left:.25rem}.tags.is-right{justify-content:flex-end}.tags.is-right .tag:not(:first-child){margin-left:.5rem}.tags.is-right .tag:not(:last-child){margin-right:0}.tags.has-addons .tag{margin-right:0}.tags.has-addons .tag:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.tags.has-addons .tag:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.tag:not(body){align-items:center;background-color:#f5f5f5;border-radius:4px;color:#4a4a4a;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:.75em;padding-right:.75em;white-space:nowrap}.tag:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}.tag:not(body).is-white{background-color:#fff;color:#0a0a0a}.tag:not(body).is-black{background-color:#0a0a0a;color:#fff}.tag:not(body).is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.tag:not(body).is-dark{background-color:#363636;color:#fff}.tag:not(body).is-primary{background-color:#00d1b2;color:#fff}.tag:not(body).is-primary.is-light{background-color:#ebfffc;color:#00947e}.tag:not(body).is-link{background-color:#485fc7;color:#fff}.tag:not(body).is-link.is-light{background-color:#eff1fa;color:#3850b7}.tag:not(body).is-info{background-color:#3e8ed0;color:#fff}.tag:not(body).is-info.is-light{background-color:#eff5fb;color:#296fa8}.tag:not(body).is-success{background-color:#48c78e;color:#fff}.tag:not(body).is-success.is-light{background-color:#effaf5;color:#257953}.tag:not(body).is-warning{background-color:#ffe08a;color:rgba(0,0,0,.7)}.tag:not(body).is-warning.is-light{background-color:#fffaeb;color:#946c00}.tag:not(body).is-danger{background-color:#f14668;color:#fff}.tag:not(body).is-danger.is-light{background-color:#feecf0;color:#cc0f35}.tag:not(body).is-normal{font-size:.75rem}.tag:not(body).is-medium{font-size:1rem}.tag:not(body).is-large{font-size:1.25rem}.tag:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}.tag:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}.tag:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}.tag:not(body).is-delete{margin-left:1px;padding:0;position:relative;width:2em}.tag:not(body).is-delete::after,.tag:not(body).is-delete::before{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.tag:not(body).is-delete::before{height:1px;width:50%}.tag:not(body).is-delete::after{height:50%;width:1px}.tag:not(body).is-delete:focus,.tag:not(body).is-delete:hover{background-color:#e8e8e8}.tag:not(body).is-delete:active{background-color:#dbdbdb}.tag:not(body).is-rounded{border-radius:9999px}a.tag:hover{text-decoration:underline}.subtitle,.title{word-break:break-word}.subtitle em,.subtitle span,.title em,.title span{font-weight:inherit}.subtitle sub,.title sub{font-size:.75em}.subtitle sup,.title sup{font-size:.75em}.subtitle .tag,.title .tag{vertical-align:middle}.title{color:#363636;font-size:2rem;font-weight:600;line-height:1.125}.title strong{color:inherit;font-weight:inherit}.title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}.title.is-1{font-size:3rem}.title.is-2{font-size:2.5rem}.title.is-3{font-size:2rem}.title.is-4{font-size:1.5rem}.title.is-5{font-size:1.25rem}.title.is-6{font-size:1rem}.title.is-7{font-size:.75rem}.subtitle{color:#4a4a4a;font-size:1.25rem;font-weight:400;line-height:1.25}.subtitle strong{color:#363636;font-weight:600}.subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}.subtitle.is-1{font-size:3rem}.subtitle.is-2{font-size:2.5rem}.subtitle.is-3{font-size:2rem}.subtitle.is-4{font-size:1.5rem}.subtitle.is-5{font-size:1.25rem}.subtitle.is-6{font-size:1rem}.subtitle.is-7{font-size:.75rem}.heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}.number{align-items:center;background-color:#f5f5f5;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:.25rem .5rem;text-align:center;vertical-align:top}.input,.select select,.textarea{background-color:#fff;border-color:#dbdbdb;border-radius:4px;color:#363636}.input::-moz-placeholder,.select select::-moz-placeholder,.textarea::-moz-placeholder{color:rgba(54,54,54,.3)}.input::-webkit-input-placeholder,.select select::-webkit-input-placeholder,.textarea::-webkit-input-placeholder{color:rgba(54,54,54,.3)}.input:-moz-placeholder,.select select:-moz-placeholder,.textarea:-moz-placeholder{color:rgba(54,54,54,.3)}.input:-ms-input-placeholder,.select select:-ms-input-placeholder,.textarea:-ms-input-placeholder{color:rgba(54,54,54,.3)}.input:hover,.is-hovered.input,.is-hovered.textarea,.select select.is-hovered,.select select:hover,.textarea:hover{border-color:#b5b5b5}.input:active,.input:focus,.is-active.input,.is-active.textarea,.is-focused.input,.is-focused.textarea,.select select.is-active,.select select.is-focused,.select select:active,.select select:focus,.textarea:active,.textarea:focus{border-color:#485fc7;box-shadow:0 0 0 .125em rgba(72,95,199,.25)}.input[disabled],.select fieldset[disabled] select,.select select[disabled],.textarea[disabled],fieldset[disabled] .input,fieldset[disabled] .select select,fieldset[disabled] .textarea{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none;color:#7a7a7a}.input[disabled]::-moz-placeholder,.select fieldset[disabled] select::-moz-placeholder,.select select[disabled]::-moz-placeholder,.textarea[disabled]::-moz-placeholder,fieldset[disabled] .input::-moz-placeholder,fieldset[disabled] .select select::-moz-placeholder,fieldset[disabled] .textarea::-moz-placeholder{color:rgba(122,122,122,.3)}.input[disabled]::-webkit-input-placeholder,.select fieldset[disabled] select::-webkit-input-placeholder,.select select[disabled]::-webkit-input-placeholder,.textarea[disabled]::-webkit-input-placeholder,fieldset[disabled] .input::-webkit-input-placeholder,fieldset[disabled] .select select::-webkit-input-placeholder,fieldset[disabled] .textarea::-webkit-input-placeholder{color:rgba(122,122,122,.3)}.input[disabled]:-moz-placeholder,.select fieldset[disabled] select:-moz-placeholder,.select select[disabled]:-moz-placeholder,.textarea[disabled]:-moz-placeholder,fieldset[disabled] .input:-moz-placeholder,fieldset[disabled] .select select:-moz-placeholder,fieldset[disabled] .textarea:-moz-placeholder{color:rgba(122,122,122,.3)}.input[disabled]:-ms-input-placeholder,.select fieldset[disabled] select:-ms-input-placeholder,.select select[disabled]:-ms-input-placeholder,.textarea[disabled]:-ms-input-placeholder,fieldset[disabled] .input:-ms-input-placeholder,fieldset[disabled] .select select:-ms-input-placeholder,fieldset[disabled] .textarea:-ms-input-placeholder{color:rgba(122,122,122,.3)}.input,.textarea{box-shadow:inset 0 .0625em .125em rgba(10,10,10,.05);max-width:100%;width:100%}.input[readonly],.textarea[readonly]{box-shadow:none}.is-white.input,.is-white.textarea{border-color:#fff}.is-white.input:active,.is-white.input:focus,.is-white.is-active.input,.is-white.is-active.textarea,.is-white.is-focused.input,.is-white.is-focused.textarea,.is-white.textarea:active,.is-white.textarea:focus{box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.is-black.input,.is-black.textarea{border-color:#0a0a0a}.is-black.input:active,.is-black.input:focus,.is-black.is-active.input,.is-black.is-active.textarea,.is-black.is-focused.input,.is-black.is-focused.textarea,.is-black.textarea:active,.is-black.textarea:focus{box-shadow:0 0 0 .125em rgba(10,10,10,.25)}.is-light.input,.is-light.textarea{border-color:#f5f5f5}.is-light.input:active,.is-light.input:focus,.is-light.is-active.input,.is-light.is-active.textarea,.is-light.is-focused.input,.is-light.is-focused.textarea,.is-light.textarea:active,.is-light.textarea:focus{box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.is-dark.input,.is-dark.textarea{border-color:#363636}.is-dark.input:active,.is-dark.input:focus,.is-dark.is-active.input,.is-dark.is-active.textarea,.is-dark.is-focused.input,.is-dark.is-focused.textarea,.is-dark.textarea:active,.is-dark.textarea:focus{box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.is-primary.input,.is-primary.textarea{border-color:#00d1b2}.is-primary.input:active,.is-primary.input:focus,.is-primary.is-active.input,.is-primary.is-active.textarea,.is-primary.is-focused.input,.is-primary.is-focused.textarea,.is-primary.textarea:active,.is-primary.textarea:focus{box-shadow:0 0 0 .125em rgba(0,209,178,.25)}.is-link.input,.is-link.textarea{border-color:#485fc7}.is-link.input:active,.is-link.input:focus,.is-link.is-active.input,.is-link.is-active.textarea,.is-link.is-focused.input,.is-link.is-focused.textarea,.is-link.textarea:active,.is-link.textarea:focus{box-shadow:0 0 0 .125em rgba(72,95,199,.25)}.is-info.input,.is-info.textarea{border-color:#3e8ed0}.is-info.input:active,.is-info.input:focus,.is-info.is-active.input,.is-info.is-active.textarea,.is-info.is-focused.input,.is-info.is-focused.textarea,.is-info.textarea:active,.is-info.textarea:focus{box-shadow:0 0 0 .125em rgba(62,142,208,.25)}.is-success.input,.is-success.textarea{border-color:#48c78e}.is-success.input:active,.is-success.input:focus,.is-success.is-active.input,.is-success.is-active.textarea,.is-success.is-focused.input,.is-success.is-focused.textarea,.is-success.textarea:active,.is-success.textarea:focus{box-shadow:0 0 0 .125em rgba(72,199,142,.25)}.is-warning.input,.is-warning.textarea{border-color:#ffe08a}.is-warning.input:active,.is-warning.input:focus,.is-warning.is-active.input,.is-warning.is-active.textarea,.is-warning.is-focused.input,.is-warning.is-focused.textarea,.is-warning.textarea:active,.is-warning.textarea:focus{box-shadow:0 0 0 .125em rgba(255,224,138,.25)}.is-danger.input,.is-danger.textarea{border-color:#f14668}.is-danger.input:active,.is-danger.input:focus,.is-danger.is-active.input,.is-danger.is-active.textarea,.is-danger.is-focused.input,.is-danger.is-focused.textarea,.is-danger.textarea:active,.is-danger.textarea:focus{box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.is-small.input,.is-small.textarea{border-radius:2px;font-size:.75rem}.is-medium.input,.is-medium.textarea{font-size:1.25rem}.is-large.input,.is-large.textarea{font-size:1.5rem}.is-fullwidth.input,.is-fullwidth.textarea{display:block;width:100%}.is-inline.input,.is-inline.textarea{display:inline;width:auto}.input.is-rounded{border-radius:9999px;padding-left:calc(calc(.75em - 1px) + .375em);padding-right:calc(calc(.75em - 1px) + .375em)}.input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}.textarea{display:block;max-width:100%;min-width:100%;padding:calc(.75em - 1px);resize:vertical}.textarea:not([rows]){max-height:40em;min-height:8em}.textarea[rows]{height:initial}.textarea.has-fixed-size{resize:none}.checkbox,.radio{cursor:pointer;display:inline-block;line-height:1.25;position:relative}.checkbox input,.radio input{cursor:pointer}.checkbox:hover,.radio:hover{color:#363636}.checkbox input[disabled],.checkbox[disabled],.radio input[disabled],.radio[disabled],fieldset[disabled] .checkbox,fieldset[disabled] .radio{color:#7a7a7a;cursor:not-allowed}.radio+.radio{margin-left:.5em}.select{display:inline-block;max-width:100%;position:relative;vertical-align:top}.select:not(.is-multiple){height:2.5em}.select:not(.is-multiple):not(.is-loading)::after{border-color:#485fc7;right:1.125em;z-index:4}.select.is-rounded select{border-radius:9999px;padding-left:1em}.select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:0}.select select::-ms-expand{display:none}.select select[disabled]:hover,fieldset[disabled] .select select:hover{border-color:#f5f5f5}.select select:not([multiple]){padding-right:2.5em}.select select[multiple]{height:auto;padding:0}.select select[multiple] option{padding:.5em 1em}.select:not(.is-multiple):not(.is-loading):hover::after{border-color:#363636}.select.is-white:not(:hover)::after{border-color:#fff}.select.is-white select{border-color:#fff}.select.is-white select.is-hovered,.select.is-white select:hover{border-color:#f2f2f2}.select.is-white select.is-active,.select.is-white select.is-focused,.select.is-white select:active,.select.is-white select:focus{box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.select.is-black:not(:hover)::after{border-color:#0a0a0a}.select.is-black select{border-color:#0a0a0a}.select.is-black select.is-hovered,.select.is-black select:hover{border-color:#000}.select.is-black select.is-active,.select.is-black select.is-focused,.select.is-black select:active,.select.is-black select:focus{box-shadow:0 0 0 .125em rgba(10,10,10,.25)}.select.is-light:not(:hover)::after{border-color:#f5f5f5}.select.is-light select{border-color:#f5f5f5}.select.is-light select.is-hovered,.select.is-light select:hover{border-color:#e8e8e8}.select.is-light select.is-active,.select.is-light select.is-focused,.select.is-light select:active,.select.is-light select:focus{box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.select.is-dark:not(:hover)::after{border-color:#363636}.select.is-dark select{border-color:#363636}.select.is-dark select.is-hovered,.select.is-dark select:hover{border-color:#292929}.select.is-dark select.is-active,.select.is-dark select.is-focused,.select.is-dark select:active,.select.is-dark select:focus{box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.select.is-primary:not(:hover)::after{border-color:#00d1b2}.select.is-primary select{border-color:#00d1b2}.select.is-primary select.is-hovered,.select.is-primary select:hover{border-color:#00b89c}.select.is-primary select.is-active,.select.is-primary select.is-focused,.select.is-primary select:active,.select.is-primary select:focus{box-shadow:0 0 0 .125em rgba(0,209,178,.25)}.select.is-link:not(:hover)::after{border-color:#485fc7}.select.is-link select{border-color:#485fc7}.select.is-link select.is-hovered,.select.is-link select:hover{border-color:#3a51bb}.select.is-link select.is-active,.select.is-link select.is-focused,.select.is-link select:active,.select.is-link select:focus{box-shadow:0 0 0 .125em rgba(72,95,199,.25)}.select.is-info:not(:hover)::after{border-color:#3e8ed0}.select.is-info select{border-color:#3e8ed0}.select.is-info select.is-hovered,.select.is-info select:hover{border-color:#3082c5}.select.is-info select.is-active,.select.is-info select.is-focused,.select.is-info select:active,.select.is-info select:focus{box-shadow:0 0 0 .125em rgba(62,142,208,.25)}.select.is-success:not(:hover)::after{border-color:#48c78e}.select.is-success select{border-color:#48c78e}.select.is-success select.is-hovered,.select.is-success select:hover{border-color:#3abb81}.select.is-success select.is-active,.select.is-success select.is-focused,.select.is-success select:active,.select.is-success select:focus{box-shadow:0 0 0 .125em rgba(72,199,142,.25)}.select.is-warning:not(:hover)::after{border-color:#ffe08a}.select.is-warning select{border-color:#ffe08a}.select.is-warning select.is-hovered,.select.is-warning select:hover{border-color:#ffd970}.select.is-warning select.is-active,.select.is-warning select.is-focused,.select.is-warning select:active,.select.is-warning select:focus{box-shadow:0 0 0 .125em rgba(255,224,138,.25)}.select.is-danger:not(:hover)::after{border-color:#f14668}.select.is-danger select{border-color:#f14668}.select.is-danger select.is-hovered,.select.is-danger select:hover{border-color:#ef2e55}.select.is-danger select.is-active,.select.is-danger select.is-focused,.select.is-danger select:active,.select.is-danger select:focus{box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.select.is-small{border-radius:2px;font-size:.75rem}.select.is-medium{font-size:1.25rem}.select.is-large{font-size:1.5rem}.select.is-disabled::after{border-color:#7a7a7a}.select.is-fullwidth{width:100%}.select.is-fullwidth select{width:100%}.select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:.625em;transform:none}.select.is-loading.is-small:after{font-size:.75rem}.select.is-loading.is-medium:after{font-size:1.25rem}.select.is-loading.is-large:after{font-size:1.5rem}.file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}.file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}.file.is-white.is-hovered .file-cta,.file.is-white:hover .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.file.is-white.is-focused .file-cta,.file.is-white:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(255,255,255,.25);color:#0a0a0a}.file.is-white.is-active .file-cta,.file.is-white:active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}.file.is-black.is-hovered .file-cta,.file.is-black:hover .file-cta{background-color:#040404;border-color:transparent;color:#fff}.file.is-black.is-focused .file-cta,.file.is-black:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(10,10,10,.25);color:#fff}.file.is-black.is-active .file-cta,.file.is-black:active .file-cta{background-color:#000;border-color:transparent;color:#fff}.file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-light.is-hovered .file-cta,.file.is-light:hover .file-cta{background-color:#eee;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-light.is-focused .file-cta,.file.is-light:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(245,245,245,.25);color:rgba(0,0,0,.7)}.file.is-light.is-active .file-cta,.file.is-light:active .file-cta{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-dark .file-cta{background-color:#363636;border-color:transparent;color:#fff}.file.is-dark.is-hovered .file-cta,.file.is-dark:hover .file-cta{background-color:#2f2f2f;border-color:transparent;color:#fff}.file.is-dark.is-focused .file-cta,.file.is-dark:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(54,54,54,.25);color:#fff}.file.is-dark.is-active .file-cta,.file.is-dark:active .file-cta{background-color:#292929;border-color:transparent;color:#fff}.file.is-primary .file-cta{background-color:#00d1b2;border-color:transparent;color:#fff}.file.is-primary.is-hovered .file-cta,.file.is-primary:hover .file-cta{background-color:#00c4a7;border-color:transparent;color:#fff}.file.is-primary.is-focused .file-cta,.file.is-primary:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(0,209,178,.25);color:#fff}.file.is-primary.is-active .file-cta,.file.is-primary:active .file-cta{background-color:#00b89c;border-color:transparent;color:#fff}.file.is-link .file-cta{background-color:#485fc7;border-color:transparent;color:#fff}.file.is-link.is-hovered .file-cta,.file.is-link:hover .file-cta{background-color:#3e56c4;border-color:transparent;color:#fff}.file.is-link.is-focused .file-cta,.file.is-link:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(72,95,199,.25);color:#fff}.file.is-link.is-active .file-cta,.file.is-link:active .file-cta{background-color:#3a51bb;border-color:transparent;color:#fff}.file.is-info .file-cta{background-color:#3e8ed0;border-color:transparent;color:#fff}.file.is-info.is-hovered .file-cta,.file.is-info:hover .file-cta{background-color:#3488ce;border-color:transparent;color:#fff}.file.is-info.is-focused .file-cta,.file.is-info:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(62,142,208,.25);color:#fff}.file.is-info.is-active .file-cta,.file.is-info:active .file-cta{background-color:#3082c5;border-color:transparent;color:#fff}.file.is-success .file-cta{background-color:#48c78e;border-color:transparent;color:#fff}.file.is-success.is-hovered .file-cta,.file.is-success:hover .file-cta{background-color:#3ec487;border-color:transparent;color:#fff}.file.is-success.is-focused .file-cta,.file.is-success:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(72,199,142,.25);color:#fff}.file.is-success.is-active .file-cta,.file.is-success:active .file-cta{background-color:#3abb81;border-color:transparent;color:#fff}.file.is-warning .file-cta{background-color:#ffe08a;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-warning.is-hovered .file-cta,.file.is-warning:hover .file-cta{background-color:#ffdc7d;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-warning.is-focused .file-cta,.file.is-warning:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(255,224,138,.25);color:rgba(0,0,0,.7)}.file.is-warning.is-active .file-cta,.file.is-warning:active .file-cta{background-color:#ffd970;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-danger .file-cta{background-color:#f14668;border-color:transparent;color:#fff}.file.is-danger.is-hovered .file-cta,.file.is-danger:hover .file-cta{background-color:#f03a5f;border-color:transparent;color:#fff}.file.is-danger.is-focused .file-cta,.file.is-danger:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(241,70,104,.25);color:#fff}.file.is-danger.is-active .file-cta,.file.is-danger:active .file-cta{background-color:#ef2e55;border-color:transparent;color:#fff}.file.is-small{font-size:.75rem}.file.is-normal{font-size:1rem}.file.is-medium{font-size:1.25rem}.file.is-medium .file-icon .fa{font-size:21px}.file.is-large{font-size:1.5rem}.file.is-large .file-icon .fa{font-size:28px}.file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}.file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}.file.has-name.is-empty .file-cta{border-radius:4px}.file.has-name.is-empty .file-name{display:none}.file.is-boxed .file-label{flex-direction:column}.file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}.file.is-boxed .file-name{border-width:0 1px 1px}.file.is-boxed .file-icon{height:1.5em;width:1.5em}.file.is-boxed .file-icon .fa{font-size:21px}.file.is-boxed.is-small .file-icon .fa{font-size:14px}.file.is-boxed.is-medium .file-icon .fa{font-size:28px}.file.is-boxed.is-large .file-icon .fa{font-size:35px}.file.is-boxed.has-name .file-cta{border-radius:4px 4px 0 0}.file.is-boxed.has-name .file-name{border-radius:0 0 4px 4px;border-width:0 1px 1px}.file.is-centered{justify-content:center}.file.is-fullwidth .file-label{width:100%}.file.is-fullwidth .file-name{flex-grow:1;max-width:none}.file.is-right{justify-content:flex-end}.file.is-right .file-cta{border-radius:0 4px 4px 0}.file.is-right .file-name{border-radius:4px 0 0 4px;border-width:1px 0 1px 1px;order:-1}.file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}.file-label:hover .file-cta{background-color:#eee;color:#363636}.file-label:hover .file-name{border-color:#d5d5d5}.file-label:active .file-cta{background-color:#e8e8e8;color:#363636}.file-label:active .file-name{border-color:#cfcfcf}.file-input{height:100%;left:0;opacity:0;outline:0;position:absolute;top:0;width:100%}.file-cta,.file-name{border-color:#dbdbdb;border-radius:4px;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}.file-cta{background-color:#f5f5f5;color:#4a4a4a}.file-name{border-color:#dbdbdb;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}.file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}.file-icon .fa{font-size:14px}.label{color:#363636;display:block;font-size:1rem;font-weight:700}.label:not(:last-child){margin-bottom:.5em}.label.is-small{font-size:.75rem}.label.is-medium{font-size:1.25rem}.label.is-large{font-size:1.5rem}.help{display:block;font-size:.75rem;margin-top:.25rem}.help.is-white{color:#fff}.help.is-black{color:#0a0a0a}.help.is-light{color:#f5f5f5}.help.is-dark{color:#363636}.help.is-primary{color:#00d1b2}.help.is-link{color:#485fc7}.help.is-info{color:#3e8ed0}.help.is-success{color:#48c78e}.help.is-warning{color:#ffe08a}.help.is-danger{color:#f14668}.field:not(:last-child){margin-bottom:.75rem}.field.has-addons{display:flex;justify-content:flex-start}.field.has-addons .control:not(:last-child){margin-right:-1px}.field.has-addons .control:not(:first-child):not(:last-child) .button,.field.has-addons .control:not(:first-child):not(:last-child) .input,.field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}.field.has-addons .control:first-child:not(:only-child) .button,.field.has-addons .control:first-child:not(:only-child) .input,.field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}.field.has-addons .control:last-child:not(:only-child) .button,.field.has-addons .control:last-child:not(:only-child) .input,.field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}.field.has-addons .control .button:not([disabled]).is-hovered,.field.has-addons .control .button:not([disabled]):hover,.field.has-addons .control .input:not([disabled]).is-hovered,.field.has-addons .control .input:not([disabled]):hover,.field.has-addons .control .select select:not([disabled]).is-hovered,.field.has-addons .control .select select:not([disabled]):hover{z-index:2}.field.has-addons .control .button:not([disabled]).is-active,.field.has-addons .control .button:not([disabled]).is-focused,.field.has-addons .control .button:not([disabled]):active,.field.has-addons .control .button:not([disabled]):focus,.field.has-addons .control .input:not([disabled]).is-active,.field.has-addons .control .input:not([disabled]).is-focused,.field.has-addons .control .input:not([disabled]):active,.field.has-addons .control .input:not([disabled]):focus,.field.has-addons .control .select select:not([disabled]).is-active,.field.has-addons .control .select select:not([disabled]).is-focused,.field.has-addons .control .select select:not([disabled]):active,.field.has-addons .control .select select:not([disabled]):focus{z-index:3}.field.has-addons .control .button:not([disabled]).is-active:hover,.field.has-addons .control .button:not([disabled]).is-focused:hover,.field.has-addons .control .button:not([disabled]):active:hover,.field.has-addons .control .button:not([disabled]):focus:hover,.field.has-addons .control .input:not([disabled]).is-active:hover,.field.has-addons .control .input:not([disabled]).is-focused:hover,.field.has-addons .control .input:not([disabled]):active:hover,.field.has-addons .control .input:not([disabled]):focus:hover,.field.has-addons .control .select select:not([disabled]).is-active:hover,.field.has-addons .control .select select:not([disabled]).is-focused:hover,.field.has-addons .control .select select:not([disabled]):active:hover,.field.has-addons .control .select select:not([disabled]):focus:hover{z-index:4}.field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}.field.has-addons.has-addons-centered{justify-content:center}.field.has-addons.has-addons-right{justify-content:flex-end}.field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}.field.is-grouped{display:flex;justify-content:flex-start}.field.is-grouped>.control{flex-shrink:0}.field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}.field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}.field.is-grouped.is-grouped-centered{justify-content:center}.field.is-grouped.is-grouped-right{justify-content:flex-end}.field.is-grouped.is-grouped-multiline{flex-wrap:wrap}.field.is-grouped.is-grouped-multiline>.control:last-child,.field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:.75rem}.field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-.75rem}.field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width:769px),print{.field.is-horizontal{display:flex}}.field-label .label{font-size:inherit}@media screen and (max-width:768px){.field-label{margin-bottom:.5rem}}@media screen and (min-width:769px),print{.field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}.field-label.is-small{font-size:.75rem;padding-top:.375em}.field-label.is-normal{padding-top:.375em}.field-label.is-medium{font-size:1.25rem;padding-top:.375em}.field-label.is-large{font-size:1.5rem;padding-top:.375em}}.field-body .field .field{margin-bottom:0}@media screen and (min-width:769px),print{.field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}.field-body .field{margin-bottom:0}.field-body>.field{flex-shrink:1}.field-body>.field:not(.is-narrow){flex-grow:1}.field-body>.field:not(:last-child){margin-right:.75rem}}.control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}.control.has-icons-left .input:focus~.icon,.control.has-icons-left .select:focus~.icon,.control.has-icons-right .input:focus~.icon,.control.has-icons-right .select:focus~.icon{color:#4a4a4a}.control.has-icons-left .input.is-small~.icon,.control.has-icons-left .select.is-small~.icon,.control.has-icons-right .input.is-small~.icon,.control.has-icons-right .select.is-small~.icon{font-size:.75rem}.control.has-icons-left .input.is-medium~.icon,.control.has-icons-left .select.is-medium~.icon,.control.has-icons-right .input.is-medium~.icon,.control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}.control.has-icons-left .input.is-large~.icon,.control.has-icons-left .select.is-large~.icon,.control.has-icons-right .input.is-large~.icon,.control.has-icons-right .select.is-large~.icon{font-size:1.5rem}.control.has-icons-left .icon,.control.has-icons-right .icon{color:#dbdbdb;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}.control.has-icons-left .input,.control.has-icons-left .select select{padding-left:2.5em}.control.has-icons-left .icon.is-left{left:0}.control.has-icons-right .input,.control.has-icons-right .select select{padding-right:2.5em}.control.has-icons-right .icon.is-right{right:0}.control.is-loading::after{position:absolute!important;right:.625em;top:.625em;z-index:4}.control.is-loading.is-small:after{font-size:.75rem}.control.is-loading.is-medium:after{font-size:1.25rem}.control.is-loading.is-large:after{font-size:1.5rem}.breadcrumb{font-size:1rem;white-space:nowrap}.breadcrumb a{align-items:center;color:#485fc7;display:flex;justify-content:center;padding:0 .75em}.breadcrumb a:hover{color:#363636}.breadcrumb li{align-items:center;display:flex}.breadcrumb li:first-child a{padding-left:0}.breadcrumb li.is-active a{color:#363636;cursor:default;pointer-events:none}.breadcrumb li+li::before{color:#b5b5b5;content:"\0002f"}.breadcrumb ol,.breadcrumb ul{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}.breadcrumb .icon:first-child{margin-right:.5em}.breadcrumb .icon:last-child{margin-left:.5em}.breadcrumb.is-centered ol,.breadcrumb.is-centered ul{justify-content:center}.breadcrumb.is-right ol,.breadcrumb.is-right ul{justify-content:flex-end}.breadcrumb.is-small{font-size:.75rem}.breadcrumb.is-medium{font-size:1.25rem}.breadcrumb.is-large{font-size:1.5rem}.breadcrumb.has-arrow-separator li+li::before{content:"\02192"}.breadcrumb.has-bullet-separator li+li::before{content:"\02022"}.breadcrumb.has-dot-separator li+li::before{content:"\000b7"}.breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}.card{background-color:#fff;border-radius:.25rem;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);color:#4a4a4a;max-width:100%;position:relative}.card-content:first-child,.card-footer:first-child,.card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-content:last-child,.card-footer:last-child,.card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.card-header{background-color:transparent;align-items:stretch;box-shadow:0 .125em .25em rgba(10,10,10,.1);display:flex}.card-header-title{align-items:center;color:#363636;display:flex;flex-grow:1;font-weight:700;padding:.75rem 1rem}.card-header-title.is-centered{justify-content:center}.card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:0 0;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:.75rem 1rem}.card-image{display:block;position:relative}.card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.card-content{background-color:transparent;padding:1.5rem}.card-footer{background-color:transparent;border-top:1px solid #ededed;align-items:stretch;display:flex}.card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}.card-footer-item:not(:last-child){border-right:1px solid #ededed}.card .media:not(:last-child){margin-bottom:1.5rem}.dropdown{display:inline-flex;position:relative;vertical-align:top}.dropdown.is-active .dropdown-menu,.dropdown.is-hoverable:hover .dropdown-menu{display:block}.dropdown.is-right .dropdown-menu{left:auto;right:0}.dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}.dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}.dropdown-content{background-color:#fff;border-radius:4px;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);padding-bottom:.5rem;padding-top:.5rem}.dropdown-item{color:#4a4a4a;display:block;font-size:.875rem;line-height:1.5;padding:.375rem 1rem;position:relative}a.dropdown-item,button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}a.dropdown-item:hover,button.dropdown-item:hover{background-color:#f5f5f5;color:#0a0a0a}a.dropdown-item.is-active,button.dropdown-item.is-active{background-color:#485fc7;color:#fff}.dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:.5rem 0}.level{align-items:center;justify-content:space-between}.level code{border-radius:4px}.level img{display:inline-block;vertical-align:top}.level.is-mobile{display:flex}.level.is-mobile .level-left,.level.is-mobile .level-right{display:flex}.level.is-mobile .level-left+.level-right{margin-top:0}.level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}.level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width:769px),print{.level{display:flex}.level>.level-item:not(.is-narrow){flex-grow:1}}.level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}.level-item .subtitle,.level-item .title{margin-bottom:0}@media screen and (max-width:768px){.level-item:not(:last-child){margin-bottom:.75rem}}.level-left,.level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.level-left .level-item.is-flexible,.level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width:769px),print{.level-left .level-item:not(:last-child),.level-right .level-item:not(:last-child){margin-right:.75rem}}.level-left{align-items:center;justify-content:flex-start}@media screen and (max-width:768px){.level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width:769px),print{.level-left{display:flex}}.level-right{align-items:center;justify-content:flex-end}@media screen and (min-width:769px),print{.level-right{display:flex}}.media{align-items:flex-start;display:flex;text-align:inherit}.media .content:not(:last-child){margin-bottom:.75rem}.media .media{border-top:1px solid rgba(219,219,219,.5);display:flex;padding-top:.75rem}.media .media .content:not(:last-child),.media .media .control:not(:last-child){margin-bottom:.5rem}.media .media .media{padding-top:.5rem}.media .media .media+.media{margin-top:.5rem}.media+.media{border-top:1px solid rgba(219,219,219,.5);margin-top:1rem;padding-top:1rem}.media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}.media-left,.media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.media-left{margin-right:1rem}.media-right{margin-left:1rem}.media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width:768px){.media-content{overflow-x:auto}}.menu{font-size:1rem}.menu.is-small{font-size:.75rem}.menu.is-medium{font-size:1.25rem}.menu.is-large{font-size:1.5rem}.menu-list{line-height:1.25}.menu-list a{border-radius:2px;color:#4a4a4a;display:block;padding:.5em .75em}.menu-list a:hover{background-color:#f5f5f5;color:#363636}.menu-list a.is-active{background-color:#485fc7;color:#fff}.menu-list li ul{border-left:1px solid #dbdbdb;margin:.75em;padding-left:.75em}.menu-label{color:#7a7a7a;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}.menu-label:not(:first-child){margin-top:1em}.menu-label:not(:last-child){margin-bottom:1em}.message{background-color:#f5f5f5;border-radius:4px;font-size:1rem}.message strong{color:currentColor}.message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}.message.is-small{font-size:.75rem}.message.is-medium{font-size:1.25rem}.message.is-large{font-size:1.5rem}.message.is-white{background-color:#fff}.message.is-white .message-header{background-color:#fff;color:#0a0a0a}.message.is-white .message-body{border-color:#fff}.message.is-black{background-color:#fafafa}.message.is-black .message-header{background-color:#0a0a0a;color:#fff}.message.is-black .message-body{border-color:#0a0a0a}.message.is-light{background-color:#fafafa}.message.is-light .message-header{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.message.is-light .message-body{border-color:#f5f5f5}.message.is-dark{background-color:#fafafa}.message.is-dark .message-header{background-color:#363636;color:#fff}.message.is-dark .message-body{border-color:#363636}.message.is-primary{background-color:#ebfffc}.message.is-primary .message-header{background-color:#00d1b2;color:#fff}.message.is-primary .message-body{border-color:#00d1b2;color:#00947e}.message.is-link{background-color:#eff1fa}.message.is-link .message-header{background-color:#485fc7;color:#fff}.message.is-link .message-body{border-color:#485fc7;color:#3850b7}.message.is-info{background-color:#eff5fb}.message.is-info .message-header{background-color:#3e8ed0;color:#fff}.message.is-info .message-body{border-color:#3e8ed0;color:#296fa8}.message.is-success{background-color:#effaf5}.message.is-success .message-header{background-color:#48c78e;color:#fff}.message.is-success .message-body{border-color:#48c78e;color:#257953}.message.is-warning{background-color:#fffaeb}.message.is-warning .message-header{background-color:#ffe08a;color:rgba(0,0,0,.7)}.message.is-warning .message-body{border-color:#ffe08a;color:#946c00}.message.is-danger{background-color:#feecf0}.message.is-danger .message-header{background-color:#f14668;color:#fff}.message.is-danger .message-body{border-color:#f14668;color:#cc0f35}.message-header{align-items:center;background-color:#4a4a4a;border-radius:4px 4px 0 0;color:#fff;display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:.75em 1em;position:relative}.message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}.message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}.message-body{border-color:#dbdbdb;border-radius:4px;border-style:solid;border-width:0 0 0 4px;color:#4a4a4a;padding:1.25em 1.5em}.message-body code,.message-body pre{background-color:#fff}.message-body pre code{background-color:transparent}.modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}.modal.is-active{display:flex}.modal-background{background-color:rgba(10,10,10,.86)}.modal-card,.modal-content{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width:769px){.modal-card,.modal-content{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}.modal-close{background:0 0;height:40px;position:fixed;right:20px;top:20px;width:40px}.modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}.modal-card-foot,.modal-card-head{align-items:center;background-color:#f5f5f5;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}.modal-card-head{border-bottom:1px solid #dbdbdb;border-top-left-radius:6px;border-top-right-radius:6px}.modal-card-title{color:#363636;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}.modal-card-foot{border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:1px solid #dbdbdb}.modal-card-foot .button:not(:last-child){margin-right:.5em}.modal-card-body{-webkit-overflow-scrolling:touch;background-color:#fff;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}.navbar{background-color:#fff;min-height:3.25rem;position:relative;z-index:30}.navbar.is-white{background-color:#fff;color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link,.navbar.is-white .navbar-brand>.navbar-item{color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link.is-active,.navbar.is-white .navbar-brand .navbar-link:focus,.navbar.is-white .navbar-brand .navbar-link:hover,.navbar.is-white .navbar-brand>a.navbar-item.is-active,.navbar.is-white .navbar-brand>a.navbar-item:focus,.navbar.is-white .navbar-brand>a.navbar-item:hover{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width:1024px){.navbar.is-white .navbar-end .navbar-link,.navbar.is-white .navbar-end>.navbar-item,.navbar.is-white .navbar-start .navbar-link,.navbar.is-white .navbar-start>.navbar-item{color:#0a0a0a}.navbar.is-white .navbar-end .navbar-link.is-active,.navbar.is-white .navbar-end .navbar-link:focus,.navbar.is-white .navbar-end .navbar-link:hover,.navbar.is-white .navbar-end>a.navbar-item.is-active,.navbar.is-white .navbar-end>a.navbar-item:focus,.navbar.is-white .navbar-end>a.navbar-item:hover,.navbar.is-white .navbar-start .navbar-link.is-active,.navbar.is-white .navbar-start .navbar-link:focus,.navbar.is-white .navbar-start .navbar-link:hover,.navbar.is-white .navbar-start>a.navbar-item.is-active,.navbar.is-white .navbar-start>a.navbar-item:focus,.navbar.is-white .navbar-start>a.navbar-item:hover{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-end .navbar-link::after,.navbar.is-white .navbar-start .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-white .navbar-item.has-dropdown:hover .navbar-link{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}.navbar.is-black{background-color:#0a0a0a;color:#fff}.navbar.is-black .navbar-brand .navbar-link,.navbar.is-black .navbar-brand>.navbar-item{color:#fff}.navbar.is-black .navbar-brand .navbar-link.is-active,.navbar.is-black .navbar-brand .navbar-link:focus,.navbar.is-black .navbar-brand .navbar-link:hover,.navbar.is-black .navbar-brand>a.navbar-item.is-active,.navbar.is-black .navbar-brand>a.navbar-item:focus,.navbar.is-black .navbar-brand>a.navbar-item:hover{background-color:#000;color:#fff}.navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-black .navbar-end .navbar-link,.navbar.is-black .navbar-end>.navbar-item,.navbar.is-black .navbar-start .navbar-link,.navbar.is-black .navbar-start>.navbar-item{color:#fff}.navbar.is-black .navbar-end .navbar-link.is-active,.navbar.is-black .navbar-end .navbar-link:focus,.navbar.is-black .navbar-end .navbar-link:hover,.navbar.is-black .navbar-end>a.navbar-item.is-active,.navbar.is-black .navbar-end>a.navbar-item:focus,.navbar.is-black .navbar-end>a.navbar-item:hover,.navbar.is-black .navbar-start .navbar-link.is-active,.navbar.is-black .navbar-start .navbar-link:focus,.navbar.is-black .navbar-start .navbar-link:hover,.navbar.is-black .navbar-start>a.navbar-item.is-active,.navbar.is-black .navbar-start>a.navbar-item:focus,.navbar.is-black .navbar-start>a.navbar-item:hover{background-color:#000;color:#fff}.navbar.is-black .navbar-end .navbar-link::after,.navbar.is-black .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-black .navbar-item.has-dropdown:hover .navbar-link{background-color:#000;color:#fff}.navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}.navbar.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand .navbar-link,.navbar.is-light .navbar-brand>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand .navbar-link.is-active,.navbar.is-light .navbar-brand .navbar-link:focus,.navbar.is-light .navbar-brand .navbar-link:hover,.navbar.is-light .navbar-brand>a.navbar-item.is-active,.navbar.is-light .navbar-brand>a.navbar-item:focus,.navbar.is-light .navbar-brand>a.navbar-item:hover{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-light .navbar-burger{color:rgba(0,0,0,.7)}@media screen and (min-width:1024px){.navbar.is-light .navbar-end .navbar-link,.navbar.is-light .navbar-end>.navbar-item,.navbar.is-light .navbar-start .navbar-link,.navbar.is-light .navbar-start>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-light .navbar-end .navbar-link.is-active,.navbar.is-light .navbar-end .navbar-link:focus,.navbar.is-light .navbar-end .navbar-link:hover,.navbar.is-light .navbar-end>a.navbar-item.is-active,.navbar.is-light .navbar-end>a.navbar-item:focus,.navbar.is-light .navbar-end>a.navbar-item:hover,.navbar.is-light .navbar-start .navbar-link.is-active,.navbar.is-light .navbar-start .navbar-link:focus,.navbar.is-light .navbar-start .navbar-link:hover,.navbar.is-light .navbar-start>a.navbar-item.is-active,.navbar.is-light .navbar-start>a.navbar-item:focus,.navbar.is-light .navbar-start>a.navbar-item:hover{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-end .navbar-link::after,.navbar.is-light .navbar-start .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-light .navbar-item.has-dropdown:hover .navbar-link{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:rgba(0,0,0,.7)}}.navbar.is-dark{background-color:#363636;color:#fff}.navbar.is-dark .navbar-brand .navbar-link,.navbar.is-dark .navbar-brand>.navbar-item{color:#fff}.navbar.is-dark .navbar-brand .navbar-link.is-active,.navbar.is-dark .navbar-brand .navbar-link:focus,.navbar.is-dark .navbar-brand .navbar-link:hover,.navbar.is-dark .navbar-brand>a.navbar-item.is-active,.navbar.is-dark .navbar-brand>a.navbar-item:focus,.navbar.is-dark .navbar-brand>a.navbar-item:hover{background-color:#292929;color:#fff}.navbar.is-dark .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-dark .navbar-end .navbar-link,.navbar.is-dark .navbar-end>.navbar-item,.navbar.is-dark .navbar-start .navbar-link,.navbar.is-dark .navbar-start>.navbar-item{color:#fff}.navbar.is-dark .navbar-end .navbar-link.is-active,.navbar.is-dark .navbar-end .navbar-link:focus,.navbar.is-dark .navbar-end .navbar-link:hover,.navbar.is-dark .navbar-end>a.navbar-item.is-active,.navbar.is-dark .navbar-end>a.navbar-item:focus,.navbar.is-dark .navbar-end>a.navbar-item:hover,.navbar.is-dark .navbar-start .navbar-link.is-active,.navbar.is-dark .navbar-start .navbar-link:focus,.navbar.is-dark .navbar-start .navbar-link:hover,.navbar.is-dark .navbar-start>a.navbar-item.is-active,.navbar.is-dark .navbar-start>a.navbar-item:focus,.navbar.is-dark .navbar-start>a.navbar-item:hover{background-color:#292929;color:#fff}.navbar.is-dark .navbar-end .navbar-link::after,.navbar.is-dark .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link{background-color:#292929;color:#fff}.navbar.is-dark .navbar-dropdown a.navbar-item.is-active{background-color:#363636;color:#fff}}.navbar.is-primary{background-color:#00d1b2;color:#fff}.navbar.is-primary .navbar-brand .navbar-link,.navbar.is-primary .navbar-brand>.navbar-item{color:#fff}.navbar.is-primary .navbar-brand .navbar-link.is-active,.navbar.is-primary .navbar-brand .navbar-link:focus,.navbar.is-primary .navbar-brand .navbar-link:hover,.navbar.is-primary .navbar-brand>a.navbar-item.is-active,.navbar.is-primary .navbar-brand>a.navbar-item:focus,.navbar.is-primary .navbar-brand>a.navbar-item:hover{background-color:#00b89c;color:#fff}.navbar.is-primary .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-primary .navbar-end .navbar-link,.navbar.is-primary .navbar-end>.navbar-item,.navbar.is-primary .navbar-start .navbar-link,.navbar.is-primary .navbar-start>.navbar-item{color:#fff}.navbar.is-primary .navbar-end .navbar-link.is-active,.navbar.is-primary .navbar-end .navbar-link:focus,.navbar.is-primary .navbar-end .navbar-link:hover,.navbar.is-primary .navbar-end>a.navbar-item.is-active,.navbar.is-primary .navbar-end>a.navbar-item:focus,.navbar.is-primary .navbar-end>a.navbar-item:hover,.navbar.is-primary .navbar-start .navbar-link.is-active,.navbar.is-primary .navbar-start .navbar-link:focus,.navbar.is-primary .navbar-start .navbar-link:hover,.navbar.is-primary .navbar-start>a.navbar-item.is-active,.navbar.is-primary .navbar-start>a.navbar-item:focus,.navbar.is-primary .navbar-start>a.navbar-item:hover{background-color:#00b89c;color:#fff}.navbar.is-primary .navbar-end .navbar-link::after,.navbar.is-primary .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link{background-color:#00b89c;color:#fff}.navbar.is-primary .navbar-dropdown a.navbar-item.is-active{background-color:#00d1b2;color:#fff}}.navbar.is-link{background-color:#485fc7;color:#fff}.navbar.is-link .navbar-brand .navbar-link,.navbar.is-link .navbar-brand>.navbar-item{color:#fff}.navbar.is-link .navbar-brand .navbar-link.is-active,.navbar.is-link .navbar-brand .navbar-link:focus,.navbar.is-link .navbar-brand .navbar-link:hover,.navbar.is-link .navbar-brand>a.navbar-item.is-active,.navbar.is-link .navbar-brand>a.navbar-item:focus,.navbar.is-link .navbar-brand>a.navbar-item:hover{background-color:#3a51bb;color:#fff}.navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-link .navbar-end .navbar-link,.navbar.is-link .navbar-end>.navbar-item,.navbar.is-link .navbar-start .navbar-link,.navbar.is-link .navbar-start>.navbar-item{color:#fff}.navbar.is-link .navbar-end .navbar-link.is-active,.navbar.is-link .navbar-end .navbar-link:focus,.navbar.is-link .navbar-end .navbar-link:hover,.navbar.is-link .navbar-end>a.navbar-item.is-active,.navbar.is-link .navbar-end>a.navbar-item:focus,.navbar.is-link .navbar-end>a.navbar-item:hover,.navbar.is-link .navbar-start .navbar-link.is-active,.navbar.is-link .navbar-start .navbar-link:focus,.navbar.is-link .navbar-start .navbar-link:hover,.navbar.is-link .navbar-start>a.navbar-item.is-active,.navbar.is-link .navbar-start>a.navbar-item:focus,.navbar.is-link .navbar-start>a.navbar-item:hover{background-color:#3a51bb;color:#fff}.navbar.is-link .navbar-end .navbar-link::after,.navbar.is-link .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-link .navbar-item.has-dropdown:hover .navbar-link{background-color:#3a51bb;color:#fff}.navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#485fc7;color:#fff}}.navbar.is-info{background-color:#3e8ed0;color:#fff}.navbar.is-info .navbar-brand .navbar-link,.navbar.is-info .navbar-brand>.navbar-item{color:#fff}.navbar.is-info .navbar-brand .navbar-link.is-active,.navbar.is-info .navbar-brand .navbar-link:focus,.navbar.is-info .navbar-brand .navbar-link:hover,.navbar.is-info .navbar-brand>a.navbar-item.is-active,.navbar.is-info .navbar-brand>a.navbar-item:focus,.navbar.is-info .navbar-brand>a.navbar-item:hover{background-color:#3082c5;color:#fff}.navbar.is-info .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-info .navbar-end .navbar-link,.navbar.is-info .navbar-end>.navbar-item,.navbar.is-info .navbar-start .navbar-link,.navbar.is-info .navbar-start>.navbar-item{color:#fff}.navbar.is-info .navbar-end .navbar-link.is-active,.navbar.is-info .navbar-end .navbar-link:focus,.navbar.is-info .navbar-end .navbar-link:hover,.navbar.is-info .navbar-end>a.navbar-item.is-active,.navbar.is-info .navbar-end>a.navbar-item:focus,.navbar.is-info .navbar-end>a.navbar-item:hover,.navbar.is-info .navbar-start .navbar-link.is-active,.navbar.is-info .navbar-start .navbar-link:focus,.navbar.is-info .navbar-start .navbar-link:hover,.navbar.is-info .navbar-start>a.navbar-item.is-active,.navbar.is-info .navbar-start>a.navbar-item:focus,.navbar.is-info .navbar-start>a.navbar-item:hover{background-color:#3082c5;color:#fff}.navbar.is-info .navbar-end .navbar-link::after,.navbar.is-info .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-info .navbar-item.has-dropdown:hover .navbar-link{background-color:#3082c5;color:#fff}.navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#3e8ed0;color:#fff}}.navbar.is-success{background-color:#48c78e;color:#fff}.navbar.is-success .navbar-brand .navbar-link,.navbar.is-success .navbar-brand>.navbar-item{color:#fff}.navbar.is-success .navbar-brand .navbar-link.is-active,.navbar.is-success .navbar-brand .navbar-link:focus,.navbar.is-success .navbar-brand .navbar-link:hover,.navbar.is-success .navbar-brand>a.navbar-item.is-active,.navbar.is-success .navbar-brand>a.navbar-item:focus,.navbar.is-success .navbar-brand>a.navbar-item:hover{background-color:#3abb81;color:#fff}.navbar.is-success .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-success .navbar-end .navbar-link,.navbar.is-success .navbar-end>.navbar-item,.navbar.is-success .navbar-start .navbar-link,.navbar.is-success .navbar-start>.navbar-item{color:#fff}.navbar.is-success .navbar-end .navbar-link.is-active,.navbar.is-success .navbar-end .navbar-link:focus,.navbar.is-success .navbar-end .navbar-link:hover,.navbar.is-success .navbar-end>a.navbar-item.is-active,.navbar.is-success .navbar-end>a.navbar-item:focus,.navbar.is-success .navbar-end>a.navbar-item:hover,.navbar.is-success .navbar-start .navbar-link.is-active,.navbar.is-success .navbar-start .navbar-link:focus,.navbar.is-success .navbar-start .navbar-link:hover,.navbar.is-success .navbar-start>a.navbar-item.is-active,.navbar.is-success .navbar-start>a.navbar-item:focus,.navbar.is-success .navbar-start>a.navbar-item:hover{background-color:#3abb81;color:#fff}.navbar.is-success .navbar-end .navbar-link::after,.navbar.is-success .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-success .navbar-item.has-dropdown:hover .navbar-link{background-color:#3abb81;color:#fff}.navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#48c78e;color:#fff}}.navbar.is-warning{background-color:#ffe08a;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand .navbar-link,.navbar.is-warning .navbar-brand>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand .navbar-link.is-active,.navbar.is-warning .navbar-brand .navbar-link:focus,.navbar.is-warning .navbar-brand .navbar-link:hover,.navbar.is-warning .navbar-brand>a.navbar-item.is-active,.navbar.is-warning .navbar-brand>a.navbar-item:focus,.navbar.is-warning .navbar-brand>a.navbar-item:hover{background-color:#ffd970;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-burger{color:rgba(0,0,0,.7)}@media screen and (min-width:1024px){.navbar.is-warning .navbar-end .navbar-link,.navbar.is-warning .navbar-end>.navbar-item,.navbar.is-warning .navbar-start .navbar-link,.navbar.is-warning .navbar-start>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-end .navbar-link.is-active,.navbar.is-warning .navbar-end .navbar-link:focus,.navbar.is-warning .navbar-end .navbar-link:hover,.navbar.is-warning .navbar-end>a.navbar-item.is-active,.navbar.is-warning .navbar-end>a.navbar-item:focus,.navbar.is-warning .navbar-end>a.navbar-item:hover,.navbar.is-warning .navbar-start .navbar-link.is-active,.navbar.is-warning .navbar-start .navbar-link:focus,.navbar.is-warning .navbar-start .navbar-link:hover,.navbar.is-warning .navbar-start>a.navbar-item.is-active,.navbar.is-warning .navbar-start>a.navbar-item:focus,.navbar.is-warning .navbar-start>a.navbar-item:hover{background-color:#ffd970;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-end .navbar-link::after,.navbar.is-warning .navbar-start .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link{background-color:#ffd970;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#ffe08a;color:rgba(0,0,0,.7)}}.navbar.is-danger{background-color:#f14668;color:#fff}.navbar.is-danger .navbar-brand .navbar-link,.navbar.is-danger .navbar-brand>.navbar-item{color:#fff}.navbar.is-danger .navbar-brand .navbar-link.is-active,.navbar.is-danger .navbar-brand .navbar-link:focus,.navbar.is-danger .navbar-brand .navbar-link:hover,.navbar.is-danger .navbar-brand>a.navbar-item.is-active,.navbar.is-danger .navbar-brand>a.navbar-item:focus,.navbar.is-danger .navbar-brand>a.navbar-item:hover{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-danger .navbar-end .navbar-link,.navbar.is-danger .navbar-end>.navbar-item,.navbar.is-danger .navbar-start .navbar-link,.navbar.is-danger .navbar-start>.navbar-item{color:#fff}.navbar.is-danger .navbar-end .navbar-link.is-active,.navbar.is-danger .navbar-end .navbar-link:focus,.navbar.is-danger .navbar-end .navbar-link:hover,.navbar.is-danger .navbar-end>a.navbar-item.is-active,.navbar.is-danger .navbar-end>a.navbar-item:focus,.navbar.is-danger .navbar-end>a.navbar-item:hover,.navbar.is-danger .navbar-start .navbar-link.is-active,.navbar.is-danger .navbar-start .navbar-link:focus,.navbar.is-danger .navbar-start .navbar-link:hover,.navbar.is-danger .navbar-start>a.navbar-item.is-active,.navbar.is-danger .navbar-start>a.navbar-item:focus,.navbar.is-danger .navbar-start>a.navbar-item:hover{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-end .navbar-link::after,.navbar.is-danger .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#f14668;color:#fff}}.navbar>.container{align-items:stretch;display:flex;min-height:3.25rem;width:100%}.navbar.has-shadow{box-shadow:0 2px 0 0 #f5f5f5}.navbar.is-fixed-bottom,.navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom{bottom:0}.navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #f5f5f5}.navbar.is-fixed-top{top:0}body.has-navbar-fixed-top,html.has-navbar-fixed-top{padding-top:3.25rem}body.has-navbar-fixed-bottom,html.has-navbar-fixed-bottom{padding-bottom:3.25rem}.navbar-brand,.navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:3.25rem}.navbar-brand a.navbar-item:focus,.navbar-brand a.navbar-item:hover{background-color:transparent}.navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}.navbar-burger{color:#4a4a4a;cursor:pointer;display:block;height:3.25rem;position:relative;width:3.25rem;margin-left:auto}.navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color,opacity,transform;transition-timing-function:ease-out;width:16px}.navbar-burger span:nth-child(1){top:calc(50% - 6px)}.navbar-burger span:nth-child(2){top:calc(50% - 1px)}.navbar-burger span:nth-child(3){top:calc(50% + 4px)}.navbar-burger:hover{background-color:rgba(0,0,0,.05)}.navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}.navbar-burger.is-active span:nth-child(2){opacity:0}.navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}.navbar-menu{display:none}.navbar-item,.navbar-link{color:#4a4a4a;display:block;line-height:1.5;padding:.5rem .75rem;position:relative}.navbar-item .icon:only-child,.navbar-link .icon:only-child{margin-left:-.25rem;margin-right:-.25rem}.navbar-link,a.navbar-item{cursor:pointer}.navbar-link.is-active,.navbar-link:focus,.navbar-link:focus-within,.navbar-link:hover,a.navbar-item.is-active,a.navbar-item:focus,a.navbar-item:focus-within,a.navbar-item:hover{background-color:#fafafa;color:#485fc7}.navbar-item{flex-grow:0;flex-shrink:0}.navbar-item img{max-height:1.75rem}.navbar-item.has-dropdown{padding:0}.navbar-item.is-expanded{flex-grow:1;flex-shrink:1}.navbar-item.is-tab{border-bottom:1px solid transparent;min-height:3.25rem;padding-bottom:calc(.5rem - 1px)}.navbar-item.is-tab:focus,.navbar-item.is-tab:hover{background-color:transparent;border-bottom-color:#485fc7}.navbar-item.is-tab.is-active{background-color:transparent;border-bottom-color:#485fc7;border-bottom-style:solid;border-bottom-width:3px;color:#485fc7;padding-bottom:calc(.5rem - 3px)}.navbar-content{flex-grow:1;flex-shrink:1}.navbar-link:not(.is-arrowless){padding-right:2.5em}.navbar-link:not(.is-arrowless)::after{border-color:#485fc7;margin-top:-.375em;right:1.125em}.navbar-dropdown{font-size:.875rem;padding-bottom:.5rem;padding-top:.5rem}.navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}.navbar-divider{background-color:#f5f5f5;border:none;display:none;height:2px;margin:.5rem 0}@media screen and (max-width:1023px){.navbar>.container{display:block}.navbar-brand .navbar-item,.navbar-tabs .navbar-item{align-items:center;display:flex}.navbar-link::after{display:none}.navbar-menu{background-color:#fff;box-shadow:0 8px 16px rgba(10,10,10,.1);padding:.5rem 0}.navbar-menu.is-active{display:block}.navbar.is-fixed-bottom-touch,.navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-touch{bottom:0}.navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,.1)}.navbar.is-fixed-top-touch{top:0}.navbar.is-fixed-top .navbar-menu,.navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 3.25rem);overflow:auto}body.has-navbar-fixed-top-touch,html.has-navbar-fixed-top-touch{padding-top:3.25rem}body.has-navbar-fixed-bottom-touch,html.has-navbar-fixed-bottom-touch{padding-bottom:3.25rem}}@media screen and (min-width:1024px){.navbar,.navbar-end,.navbar-menu,.navbar-start{align-items:stretch;display:flex}.navbar{min-height:3.25rem}.navbar.is-spaced{padding:1rem 2rem}.navbar.is-spaced .navbar-end,.navbar.is-spaced .navbar-start{align-items:center}.navbar.is-spaced .navbar-link,.navbar.is-spaced a.navbar-item{border-radius:4px}.navbar.is-transparent .navbar-link.is-active,.navbar.is-transparent .navbar-link:focus,.navbar.is-transparent .navbar-link:hover,.navbar.is-transparent a.navbar-item.is-active,.navbar.is-transparent a.navbar-item:focus,.navbar.is-transparent a.navbar-item:hover{background-color:transparent!important}.navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent!important}.navbar.is-transparent .navbar-dropdown a.navbar-item:focus,.navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#485fc7}.navbar-burger{display:none}.navbar-item,.navbar-link{align-items:center;display:flex}.navbar-item.has-dropdown{align-items:stretch}.navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(.25em,-.25em)}.navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:2px solid #dbdbdb;border-radius:6px 6px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,.1);top:auto}.navbar-item.is-active .navbar-dropdown,.navbar-item.is-hoverable:focus .navbar-dropdown,.navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar-item.is-active .navbar-dropdown.is-boxed,.navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-active .navbar-dropdown,.navbar.is-spaced .navbar-item.is-hoverable:focus .navbar-dropdown,.navbar.is-spaced .navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar.is-spaced .navbar-item.is-hoverable:hover .navbar-dropdown{opacity:1;pointer-events:auto;transform:translateY(0)}.navbar-menu{flex-grow:1;flex-shrink:0}.navbar-start{justify-content:flex-start;margin-right:auto}.navbar-end{justify-content:flex-end;margin-left:auto}.navbar-dropdown{background-color:#fff;border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:2px solid #dbdbdb;box-shadow:0 8px 8px rgba(10,10,10,.1);display:none;font-size:.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}.navbar-dropdown .navbar-item{padding:.375rem 1rem;white-space:nowrap}.navbar-dropdown a.navbar-item{padding-right:3rem}.navbar-dropdown a.navbar-item:focus,.navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#485fc7}.navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-dropdown{border-radius:6px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity,transform}.navbar-dropdown.is-right{left:auto;right:0}.navbar-divider{display:block}.container>.navbar .navbar-brand,.navbar>.container .navbar-brand{margin-left:-.75rem}.container>.navbar .navbar-menu,.navbar>.container .navbar-menu{margin-right:-.75rem}.navbar.is-fixed-bottom-desktop,.navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-desktop{bottom:0}.navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,.1)}.navbar.is-fixed-top-desktop{top:0}body.has-navbar-fixed-top-desktop,html.has-navbar-fixed-top-desktop{padding-top:3.25rem}body.has-navbar-fixed-bottom-desktop,html.has-navbar-fixed-bottom-desktop{padding-bottom:3.25rem}body.has-spaced-navbar-fixed-top,html.has-spaced-navbar-fixed-top{padding-top:5.25rem}body.has-spaced-navbar-fixed-bottom,html.has-spaced-navbar-fixed-bottom{padding-bottom:5.25rem}.navbar-link.is-active,a.navbar-item.is-active{color:#0a0a0a}.navbar-link.is-active:not(:focus):not(:hover),a.navbar-item.is-active:not(:focus):not(:hover){background-color:transparent}.navbar-item.has-dropdown.is-active .navbar-link,.navbar-item.has-dropdown:focus .navbar-link,.navbar-item.has-dropdown:hover .navbar-link{background-color:#fafafa}}.hero.is-fullheight-with-navbar{min-height:calc(100vh - 3.25rem)}.pagination{font-size:1rem;margin:-.25rem}.pagination.is-small{font-size:.75rem}.pagination.is-medium{font-size:1.25rem}.pagination.is-large{font-size:1.5rem}.pagination.is-rounded .pagination-next,.pagination.is-rounded .pagination-previous{padding-left:1em;padding-right:1em;border-radius:9999px}.pagination.is-rounded .pagination-link{border-radius:9999px}.pagination,.pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}.pagination-ellipsis,.pagination-link,.pagination-next,.pagination-previous{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}.pagination-link,.pagination-next,.pagination-previous{border-color:#dbdbdb;color:#363636;min-width:2.5em}.pagination-link:hover,.pagination-next:hover,.pagination-previous:hover{border-color:#b5b5b5;color:#363636}.pagination-link:focus,.pagination-next:focus,.pagination-previous:focus{border-color:#485fc7}.pagination-link:active,.pagination-next:active,.pagination-previous:active{box-shadow:inset 0 1px 2px rgba(10,10,10,.2)}.pagination-link[disabled],.pagination-next[disabled],.pagination-previous[disabled]{background-color:#dbdbdb;border-color:#dbdbdb;box-shadow:none;color:#7a7a7a;opacity:.5}.pagination-next,.pagination-previous{padding-left:.75em;padding-right:.75em;white-space:nowrap}.pagination-link.is-current{background-color:#485fc7;border-color:#485fc7;color:#fff}.pagination-ellipsis{color:#b5b5b5;pointer-events:none}.pagination-list{flex-wrap:wrap}.pagination-list li{list-style:none}@media screen and (max-width:768px){.pagination{flex-wrap:wrap}.pagination-next,.pagination-previous{flex-grow:1;flex-shrink:1}.pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width:769px),print{.pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}.pagination-ellipsis,.pagination-link,.pagination-next,.pagination-previous{margin-bottom:0;margin-top:0}.pagination-previous{order:2}.pagination-next{order:3}.pagination{justify-content:space-between;margin-bottom:0;margin-top:0}.pagination.is-centered .pagination-previous{order:1}.pagination.is-centered .pagination-list{justify-content:center;order:2}.pagination.is-centered .pagination-next{order:3}.pagination.is-right .pagination-previous{order:1}.pagination.is-right .pagination-next{order:2}.pagination.is-right .pagination-list{justify-content:flex-end;order:3}}.panel{border-radius:6px;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);font-size:1rem}.panel:not(:last-child){margin-bottom:1.5rem}.panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}.panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}.panel.is-white .panel-block.is-active .panel-icon{color:#fff}.panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}.panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}.panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}.panel.is-light .panel-heading{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.panel.is-light .panel-tabs a.is-active{border-bottom-color:#f5f5f5}.panel.is-light .panel-block.is-active .panel-icon{color:#f5f5f5}.panel.is-dark .panel-heading{background-color:#363636;color:#fff}.panel.is-dark .panel-tabs a.is-active{border-bottom-color:#363636}.panel.is-dark .panel-block.is-active .panel-icon{color:#363636}.panel.is-primary .panel-heading{background-color:#00d1b2;color:#fff}.panel.is-primary .panel-tabs a.is-active{border-bottom-color:#00d1b2}.panel.is-primary .panel-block.is-active .panel-icon{color:#00d1b2}.panel.is-link .panel-heading{background-color:#485fc7;color:#fff}.panel.is-link .panel-tabs a.is-active{border-bottom-color:#485fc7}.panel.is-link .panel-block.is-active .panel-icon{color:#485fc7}.panel.is-info .panel-heading{background-color:#3e8ed0;color:#fff}.panel.is-info .panel-tabs a.is-active{border-bottom-color:#3e8ed0}.panel.is-info .panel-block.is-active .panel-icon{color:#3e8ed0}.panel.is-success .panel-heading{background-color:#48c78e;color:#fff}.panel.is-success .panel-tabs a.is-active{border-bottom-color:#48c78e}.panel.is-success .panel-block.is-active .panel-icon{color:#48c78e}.panel.is-warning .panel-heading{background-color:#ffe08a;color:rgba(0,0,0,.7)}.panel.is-warning .panel-tabs a.is-active{border-bottom-color:#ffe08a}.panel.is-warning .panel-block.is-active .panel-icon{color:#ffe08a}.panel.is-danger .panel-heading{background-color:#f14668;color:#fff}.panel.is-danger .panel-tabs a.is-active{border-bottom-color:#f14668}.panel.is-danger .panel-block.is-active .panel-icon{color:#f14668}.panel-block:not(:last-child),.panel-tabs:not(:last-child){border-bottom:1px solid #ededed}.panel-heading{background-color:#ededed;border-radius:6px 6px 0 0;color:#363636;font-size:1.25em;font-weight:700;line-height:1.25;padding:.75em 1em}.panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}.panel-tabs a{border-bottom:1px solid #dbdbdb;margin-bottom:-1px;padding:.5em}.panel-tabs a.is-active{border-bottom-color:#4a4a4a;color:#363636}.panel-list a{color:#4a4a4a}.panel-list a:hover{color:#485fc7}.panel-block{align-items:center;color:#363636;display:flex;justify-content:flex-start;padding:.5em .75em}.panel-block input[type=checkbox]{margin-right:.75em}.panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}.panel-block.is-wrapped{flex-wrap:wrap}.panel-block.is-active{border-left-color:#485fc7;color:#363636}.panel-block.is-active .panel-icon{color:#485fc7}.panel-block:last-child{border-bottom-left-radius:6px;border-bottom-right-radius:6px}a.panel-block,label.panel-block{cursor:pointer}a.panel-block:hover,label.panel-block:hover{background-color:#f5f5f5}.panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#7a7a7a;margin-right:.75em}.panel-icon .fa{font-size:inherit;line-height:inherit}.tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}.tabs a{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;color:#4a4a4a;display:flex;justify-content:center;margin-bottom:-1px;padding:.5em 1em;vertical-align:top}.tabs a:hover{border-bottom-color:#363636;color:#363636}.tabs li{display:block}.tabs li.is-active a{border-bottom-color:#485fc7;color:#485fc7}.tabs ul{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}.tabs ul.is-left{padding-right:.75em}.tabs ul.is-center{flex:none;justify-content:center;padding-left:.75em;padding-right:.75em}.tabs ul.is-right{justify-content:flex-end;padding-left:.75em}.tabs .icon:first-child{margin-right:.5em}.tabs .icon:last-child{margin-left:.5em}.tabs.is-centered ul{justify-content:center}.tabs.is-right ul{justify-content:flex-end}.tabs.is-boxed a{border:1px solid transparent;border-radius:4px 4px 0 0}.tabs.is-boxed a:hover{background-color:#f5f5f5;border-bottom-color:#dbdbdb}.tabs.is-boxed li.is-active a{background-color:#fff;border-color:#dbdbdb;border-bottom-color:transparent!important}.tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}.tabs.is-toggle a{border-color:#dbdbdb;border-style:solid;border-width:1px;margin-bottom:0;position:relative}.tabs.is-toggle a:hover{background-color:#f5f5f5;border-color:#b5b5b5;z-index:2}.tabs.is-toggle li+li{margin-left:-1px}.tabs.is-toggle li:first-child a{border-top-left-radius:4px;border-bottom-left-radius:4px}.tabs.is-toggle li:last-child a{border-top-right-radius:4px;border-bottom-right-radius:4px}.tabs.is-toggle li.is-active a{background-color:#485fc7;border-color:#485fc7;color:#fff;z-index:1}.tabs.is-toggle ul{border-bottom:none}.tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}.tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}.tabs.is-small{font-size:.75rem}.tabs.is-medium{font-size:1.25rem}.tabs.is-large{font-size:1.5rem}.column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>.column.is-narrow{flex:none;width:unset}.columns.is-mobile>.column.is-full{flex:none;width:100%}.columns.is-mobile>.column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>.column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>.column.is-half{flex:none;width:50%}.columns.is-mobile>.column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>.column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>.column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>.column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>.column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>.column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>.column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>.column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>.column.is-offset-half{margin-left:50%}.columns.is-mobile>.column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>.column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>.column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>.column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>.column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>.column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>.column.is-0{flex:none;width:0%}.columns.is-mobile>.column.is-offset-0{margin-left:0}.columns.is-mobile>.column.is-1{flex:none;width:8.33333%}.columns.is-mobile>.column.is-offset-1{margin-left:8.33333%}.columns.is-mobile>.column.is-2{flex:none;width:16.66667%}.columns.is-mobile>.column.is-offset-2{margin-left:16.66667%}.columns.is-mobile>.column.is-3{flex:none;width:25%}.columns.is-mobile>.column.is-offset-3{margin-left:25%}.columns.is-mobile>.column.is-4{flex:none;width:33.33333%}.columns.is-mobile>.column.is-offset-4{margin-left:33.33333%}.columns.is-mobile>.column.is-5{flex:none;width:41.66667%}.columns.is-mobile>.column.is-offset-5{margin-left:41.66667%}.columns.is-mobile>.column.is-6{flex:none;width:50%}.columns.is-mobile>.column.is-offset-6{margin-left:50%}.columns.is-mobile>.column.is-7{flex:none;width:58.33333%}.columns.is-mobile>.column.is-offset-7{margin-left:58.33333%}.columns.is-mobile>.column.is-8{flex:none;width:66.66667%}.columns.is-mobile>.column.is-offset-8{margin-left:66.66667%}.columns.is-mobile>.column.is-9{flex:none;width:75%}.columns.is-mobile>.column.is-offset-9{margin-left:75%}.columns.is-mobile>.column.is-10{flex:none;width:83.33333%}.columns.is-mobile>.column.is-offset-10{margin-left:83.33333%}.columns.is-mobile>.column.is-11{flex:none;width:91.66667%}.columns.is-mobile>.column.is-offset-11{margin-left:91.66667%}.columns.is-mobile>.column.is-12{flex:none;width:100%}.columns.is-mobile>.column.is-offset-12{margin-left:100%}@media screen and (max-width:768px){.column.is-narrow-mobile{flex:none;width:unset}.column.is-full-mobile{flex:none;width:100%}.column.is-three-quarters-mobile{flex:none;width:75%}.column.is-two-thirds-mobile{flex:none;width:66.6666%}.column.is-half-mobile{flex:none;width:50%}.column.is-one-third-mobile{flex:none;width:33.3333%}.column.is-one-quarter-mobile{flex:none;width:25%}.column.is-one-fifth-mobile{flex:none;width:20%}.column.is-two-fifths-mobile{flex:none;width:40%}.column.is-three-fifths-mobile{flex:none;width:60%}.column.is-four-fifths-mobile{flex:none;width:80%}.column.is-offset-three-quarters-mobile{margin-left:75%}.column.is-offset-two-thirds-mobile{margin-left:66.6666%}.column.is-offset-half-mobile{margin-left:50%}.column.is-offset-one-third-mobile{margin-left:33.3333%}.column.is-offset-one-quarter-mobile{margin-left:25%}.column.is-offset-one-fifth-mobile{margin-left:20%}.column.is-offset-two-fifths-mobile{margin-left:40%}.column.is-offset-three-fifths-mobile{margin-left:60%}.column.is-offset-four-fifths-mobile{margin-left:80%}.column.is-0-mobile{flex:none;width:0%}.column.is-offset-0-mobile{margin-left:0}.column.is-1-mobile{flex:none;width:8.33333%}.column.is-offset-1-mobile{margin-left:8.33333%}.column.is-2-mobile{flex:none;width:16.66667%}.column.is-offset-2-mobile{margin-left:16.66667%}.column.is-3-mobile{flex:none;width:25%}.column.is-offset-3-mobile{margin-left:25%}.column.is-4-mobile{flex:none;width:33.33333%}.column.is-offset-4-mobile{margin-left:33.33333%}.column.is-5-mobile{flex:none;width:41.66667%}.column.is-offset-5-mobile{margin-left:41.66667%}.column.is-6-mobile{flex:none;width:50%}.column.is-offset-6-mobile{margin-left:50%}.column.is-7-mobile{flex:none;width:58.33333%}.column.is-offset-7-mobile{margin-left:58.33333%}.column.is-8-mobile{flex:none;width:66.66667%}.column.is-offset-8-mobile{margin-left:66.66667%}.column.is-9-mobile{flex:none;width:75%}.column.is-offset-9-mobile{margin-left:75%}.column.is-10-mobile{flex:none;width:83.33333%}.column.is-offset-10-mobile{margin-left:83.33333%}.column.is-11-mobile{flex:none;width:91.66667%}.column.is-offset-11-mobile{margin-left:91.66667%}.column.is-12-mobile{flex:none;width:100%}.column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width:769px),print{.column.is-narrow,.column.is-narrow-tablet{flex:none;width:unset}.column.is-full,.column.is-full-tablet{flex:none;width:100%}.column.is-three-quarters,.column.is-three-quarters-tablet{flex:none;width:75%}.column.is-two-thirds,.column.is-two-thirds-tablet{flex:none;width:66.6666%}.column.is-half,.column.is-half-tablet{flex:none;width:50%}.column.is-one-third,.column.is-one-third-tablet{flex:none;width:33.3333%}.column.is-one-quarter,.column.is-one-quarter-tablet{flex:none;width:25%}.column.is-one-fifth,.column.is-one-fifth-tablet{flex:none;width:20%}.column.is-two-fifths,.column.is-two-fifths-tablet{flex:none;width:40%}.column.is-three-fifths,.column.is-three-fifths-tablet{flex:none;width:60%}.column.is-four-fifths,.column.is-four-fifths-tablet{flex:none;width:80%}.column.is-offset-three-quarters,.column.is-offset-three-quarters-tablet{margin-left:75%}.column.is-offset-two-thirds,.column.is-offset-two-thirds-tablet{margin-left:66.6666%}.column.is-offset-half,.column.is-offset-half-tablet{margin-left:50%}.column.is-offset-one-third,.column.is-offset-one-third-tablet{margin-left:33.3333%}.column.is-offset-one-quarter,.column.is-offset-one-quarter-tablet{margin-left:25%}.column.is-offset-one-fifth,.column.is-offset-one-fifth-tablet{margin-left:20%}.column.is-offset-two-fifths,.column.is-offset-two-fifths-tablet{margin-left:40%}.column.is-offset-three-fifths,.column.is-offset-three-fifths-tablet{margin-left:60%}.column.is-offset-four-fifths,.column.is-offset-four-fifths-tablet{margin-left:80%}.column.is-0,.column.is-0-tablet{flex:none;width:0%}.column.is-offset-0,.column.is-offset-0-tablet{margin-left:0}.column.is-1,.column.is-1-tablet{flex:none;width:8.33333%}.column.is-offset-1,.column.is-offset-1-tablet{margin-left:8.33333%}.column.is-2,.column.is-2-tablet{flex:none;width:16.66667%}.column.is-offset-2,.column.is-offset-2-tablet{margin-left:16.66667%}.column.is-3,.column.is-3-tablet{flex:none;width:25%}.column.is-offset-3,.column.is-offset-3-tablet{margin-left:25%}.column.is-4,.column.is-4-tablet{flex:none;width:33.33333%}.column.is-offset-4,.column.is-offset-4-tablet{margin-left:33.33333%}.column.is-5,.column.is-5-tablet{flex:none;width:41.66667%}.column.is-offset-5,.column.is-offset-5-tablet{margin-left:41.66667%}.column.is-6,.column.is-6-tablet{flex:none;width:50%}.column.is-offset-6,.column.is-offset-6-tablet{margin-left:50%}.column.is-7,.column.is-7-tablet{flex:none;width:58.33333%}.column.is-offset-7,.column.is-offset-7-tablet{margin-left:58.33333%}.column.is-8,.column.is-8-tablet{flex:none;width:66.66667%}.column.is-offset-8,.column.is-offset-8-tablet{margin-left:66.66667%}.column.is-9,.column.is-9-tablet{flex:none;width:75%}.column.is-offset-9,.column.is-offset-9-tablet{margin-left:75%}.column.is-10,.column.is-10-tablet{flex:none;width:83.33333%}.column.is-offset-10,.column.is-offset-10-tablet{margin-left:83.33333%}.column.is-11,.column.is-11-tablet{flex:none;width:91.66667%}.column.is-offset-11,.column.is-offset-11-tablet{margin-left:91.66667%}.column.is-12,.column.is-12-tablet{flex:none;width:100%}.column.is-offset-12,.column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width:1023px){.column.is-narrow-touch{flex:none;width:unset}.column.is-full-touch{flex:none;width:100%}.column.is-three-quarters-touch{flex:none;width:75%}.column.is-two-thirds-touch{flex:none;width:66.6666%}.column.is-half-touch{flex:none;width:50%}.column.is-one-third-touch{flex:none;width:33.3333%}.column.is-one-quarter-touch{flex:none;width:25%}.column.is-one-fifth-touch{flex:none;width:20%}.column.is-two-fifths-touch{flex:none;width:40%}.column.is-three-fifths-touch{flex:none;width:60%}.column.is-four-fifths-touch{flex:none;width:80%}.column.is-offset-three-quarters-touch{margin-left:75%}.column.is-offset-two-thirds-touch{margin-left:66.6666%}.column.is-offset-half-touch{margin-left:50%}.column.is-offset-one-third-touch{margin-left:33.3333%}.column.is-offset-one-quarter-touch{margin-left:25%}.column.is-offset-one-fifth-touch{margin-left:20%}.column.is-offset-two-fifths-touch{margin-left:40%}.column.is-offset-three-fifths-touch{margin-left:60%}.column.is-offset-four-fifths-touch{margin-left:80%}.column.is-0-touch{flex:none;width:0%}.column.is-offset-0-touch{margin-left:0}.column.is-1-touch{flex:none;width:8.33333%}.column.is-offset-1-touch{margin-left:8.33333%}.column.is-2-touch{flex:none;width:16.66667%}.column.is-offset-2-touch{margin-left:16.66667%}.column.is-3-touch{flex:none;width:25%}.column.is-offset-3-touch{margin-left:25%}.column.is-4-touch{flex:none;width:33.33333%}.column.is-offset-4-touch{margin-left:33.33333%}.column.is-5-touch{flex:none;width:41.66667%}.column.is-offset-5-touch{margin-left:41.66667%}.column.is-6-touch{flex:none;width:50%}.column.is-offset-6-touch{margin-left:50%}.column.is-7-touch{flex:none;width:58.33333%}.column.is-offset-7-touch{margin-left:58.33333%}.column.is-8-touch{flex:none;width:66.66667%}.column.is-offset-8-touch{margin-left:66.66667%}.column.is-9-touch{flex:none;width:75%}.column.is-offset-9-touch{margin-left:75%}.column.is-10-touch{flex:none;width:83.33333%}.column.is-offset-10-touch{margin-left:83.33333%}.column.is-11-touch{flex:none;width:91.66667%}.column.is-offset-11-touch{margin-left:91.66667%}.column.is-12-touch{flex:none;width:100%}.column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width:1024px){.column.is-narrow-desktop{flex:none;width:unset}.column.is-full-desktop{flex:none;width:100%}.column.is-three-quarters-desktop{flex:none;width:75%}.column.is-two-thirds-desktop{flex:none;width:66.6666%}.column.is-half-desktop{flex:none;width:50%}.column.is-one-third-desktop{flex:none;width:33.3333%}.column.is-one-quarter-desktop{flex:none;width:25%}.column.is-one-fifth-desktop{flex:none;width:20%}.column.is-two-fifths-desktop{flex:none;width:40%}.column.is-three-fifths-desktop{flex:none;width:60%}.column.is-four-fifths-desktop{flex:none;width:80%}.column.is-offset-three-quarters-desktop{margin-left:75%}.column.is-offset-two-thirds-desktop{margin-left:66.6666%}.column.is-offset-half-desktop{margin-left:50%}.column.is-offset-one-third-desktop{margin-left:33.3333%}.column.is-offset-one-quarter-desktop{margin-left:25%}.column.is-offset-one-fifth-desktop{margin-left:20%}.column.is-offset-two-fifths-desktop{margin-left:40%}.column.is-offset-three-fifths-desktop{margin-left:60%}.column.is-offset-four-fifths-desktop{margin-left:80%}.column.is-0-desktop{flex:none;width:0%}.column.is-offset-0-desktop{margin-left:0}.column.is-1-desktop{flex:none;width:8.33333%}.column.is-offset-1-desktop{margin-left:8.33333%}.column.is-2-desktop{flex:none;width:16.66667%}.column.is-offset-2-desktop{margin-left:16.66667%}.column.is-3-desktop{flex:none;width:25%}.column.is-offset-3-desktop{margin-left:25%}.column.is-4-desktop{flex:none;width:33.33333%}.column.is-offset-4-desktop{margin-left:33.33333%}.column.is-5-desktop{flex:none;width:41.66667%}.column.is-offset-5-desktop{margin-left:41.66667%}.column.is-6-desktop{flex:none;width:50%}.column.is-offset-6-desktop{margin-left:50%}.column.is-7-desktop{flex:none;width:58.33333%}.column.is-offset-7-desktop{margin-left:58.33333%}.column.is-8-desktop{flex:none;width:66.66667%}.column.is-offset-8-desktop{margin-left:66.66667%}.column.is-9-desktop{flex:none;width:75%}.column.is-offset-9-desktop{margin-left:75%}.column.is-10-desktop{flex:none;width:83.33333%}.column.is-offset-10-desktop{margin-left:83.33333%}.column.is-11-desktop{flex:none;width:91.66667%}.column.is-offset-11-desktop{margin-left:91.66667%}.column.is-12-desktop{flex:none;width:100%}.column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width:1216px){.column.is-narrow-widescreen{flex:none;width:unset}.column.is-full-widescreen{flex:none;width:100%}.column.is-three-quarters-widescreen{flex:none;width:75%}.column.is-two-thirds-widescreen{flex:none;width:66.6666%}.column.is-half-widescreen{flex:none;width:50%}.column.is-one-third-widescreen{flex:none;width:33.3333%}.column.is-one-quarter-widescreen{flex:none;width:25%}.column.is-one-fifth-widescreen{flex:none;width:20%}.column.is-two-fifths-widescreen{flex:none;width:40%}.column.is-three-fifths-widescreen{flex:none;width:60%}.column.is-four-fifths-widescreen{flex:none;width:80%}.column.is-offset-three-quarters-widescreen{margin-left:75%}.column.is-offset-two-thirds-widescreen{margin-left:66.6666%}.column.is-offset-half-widescreen{margin-left:50%}.column.is-offset-one-third-widescreen{margin-left:33.3333%}.column.is-offset-one-quarter-widescreen{margin-left:25%}.column.is-offset-one-fifth-widescreen{margin-left:20%}.column.is-offset-two-fifths-widescreen{margin-left:40%}.column.is-offset-three-fifths-widescreen{margin-left:60%}.column.is-offset-four-fifths-widescreen{margin-left:80%}.column.is-0-widescreen{flex:none;width:0%}.column.is-offset-0-widescreen{margin-left:0}.column.is-1-widescreen{flex:none;width:8.33333%}.column.is-offset-1-widescreen{margin-left:8.33333%}.column.is-2-widescreen{flex:none;width:16.66667%}.column.is-offset-2-widescreen{margin-left:16.66667%}.column.is-3-widescreen{flex:none;width:25%}.column.is-offset-3-widescreen{margin-left:25%}.column.is-4-widescreen{flex:none;width:33.33333%}.column.is-offset-4-widescreen{margin-left:33.33333%}.column.is-5-widescreen{flex:none;width:41.66667%}.column.is-offset-5-widescreen{margin-left:41.66667%}.column.is-6-widescreen{flex:none;width:50%}.column.is-offset-6-widescreen{margin-left:50%}.column.is-7-widescreen{flex:none;width:58.33333%}.column.is-offset-7-widescreen{margin-left:58.33333%}.column.is-8-widescreen{flex:none;width:66.66667%}.column.is-offset-8-widescreen{margin-left:66.66667%}.column.is-9-widescreen{flex:none;width:75%}.column.is-offset-9-widescreen{margin-left:75%}.column.is-10-widescreen{flex:none;width:83.33333%}.column.is-offset-10-widescreen{margin-left:83.33333%}.column.is-11-widescreen{flex:none;width:91.66667%}.column.is-offset-11-widescreen{margin-left:91.66667%}.column.is-12-widescreen{flex:none;width:100%}.column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width:1408px){.column.is-narrow-fullhd{flex:none;width:unset}.column.is-full-fullhd{flex:none;width:100%}.column.is-three-quarters-fullhd{flex:none;width:75%}.column.is-two-thirds-fullhd{flex:none;width:66.6666%}.column.is-half-fullhd{flex:none;width:50%}.column.is-one-third-fullhd{flex:none;width:33.3333%}.column.is-one-quarter-fullhd{flex:none;width:25%}.column.is-one-fifth-fullhd{flex:none;width:20%}.column.is-two-fifths-fullhd{flex:none;width:40%}.column.is-three-fifths-fullhd{flex:none;width:60%}.column.is-four-fifths-fullhd{flex:none;width:80%}.column.is-offset-three-quarters-fullhd{margin-left:75%}.column.is-offset-two-thirds-fullhd{margin-left:66.6666%}.column.is-offset-half-fullhd{margin-left:50%}.column.is-offset-one-third-fullhd{margin-left:33.3333%}.column.is-offset-one-quarter-fullhd{margin-left:25%}.column.is-offset-one-fifth-fullhd{margin-left:20%}.column.is-offset-two-fifths-fullhd{margin-left:40%}.column.is-offset-three-fifths-fullhd{margin-left:60%}.column.is-offset-four-fifths-fullhd{margin-left:80%}.column.is-0-fullhd{flex:none;width:0%}.column.is-offset-0-fullhd{margin-left:0}.column.is-1-fullhd{flex:none;width:8.33333%}.column.is-offset-1-fullhd{margin-left:8.33333%}.column.is-2-fullhd{flex:none;width:16.66667%}.column.is-offset-2-fullhd{margin-left:16.66667%}.column.is-3-fullhd{flex:none;width:25%}.column.is-offset-3-fullhd{margin-left:25%}.column.is-4-fullhd{flex:none;width:33.33333%}.column.is-offset-4-fullhd{margin-left:33.33333%}.column.is-5-fullhd{flex:none;width:41.66667%}.column.is-offset-5-fullhd{margin-left:41.66667%}.column.is-6-fullhd{flex:none;width:50%}.column.is-offset-6-fullhd{margin-left:50%}.column.is-7-fullhd{flex:none;width:58.33333%}.column.is-offset-7-fullhd{margin-left:58.33333%}.column.is-8-fullhd{flex:none;width:66.66667%}.column.is-offset-8-fullhd{margin-left:66.66667%}.column.is-9-fullhd{flex:none;width:75%}.column.is-offset-9-fullhd{margin-left:75%}.column.is-10-fullhd{flex:none;width:83.33333%}.column.is-offset-10-fullhd{margin-left:83.33333%}.column.is-11-fullhd{flex:none;width:91.66667%}.column.is-offset-11-fullhd{margin-left:91.66667%}.column.is-12-fullhd{flex:none;width:100%}.column.is-offset-12-fullhd{margin-left:100%}}.columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.columns:last-child{margin-bottom:-.75rem}.columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}.columns.is-centered{justify-content:center}.columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}.columns.is-gapless>.column{margin:0;padding:0!important}.columns.is-gapless:not(:last-child){margin-bottom:1.5rem}.columns.is-gapless:last-child{margin-bottom:0}.columns.is-mobile{display:flex}.columns.is-multiline{flex-wrap:wrap}.columns.is-vcentered{align-items:center}@media screen and (min-width:769px),print{.columns:not(.is-desktop){display:flex}}@media screen and (min-width:1024px){.columns.is-desktop{display:flex}}.columns.is-variable{--columnGap:0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}.columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}.columns.is-variable.is-0{--columnGap:0rem}@media screen and (max-width:768px){.columns.is-variable.is-0-mobile{--columnGap:0rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-0-tablet{--columnGap:0rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-0-tablet-only{--columnGap:0rem}}@media screen and (max-width:1023px){.columns.is-variable.is-0-touch{--columnGap:0rem}}@media screen and (min-width:1024px){.columns.is-variable.is-0-desktop{--columnGap:0rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-0-desktop-only{--columnGap:0rem}}@media screen and (min-width:1216px){.columns.is-variable.is-0-widescreen{--columnGap:0rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-0-widescreen-only{--columnGap:0rem}}@media screen and (min-width:1408px){.columns.is-variable.is-0-fullhd{--columnGap:0rem}}.columns.is-variable.is-1{--columnGap:0.25rem}@media screen and (max-width:768px){.columns.is-variable.is-1-mobile{--columnGap:0.25rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-1-tablet{--columnGap:0.25rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-1-tablet-only{--columnGap:0.25rem}}@media screen and (max-width:1023px){.columns.is-variable.is-1-touch{--columnGap:0.25rem}}@media screen and (min-width:1024px){.columns.is-variable.is-1-desktop{--columnGap:0.25rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-1-desktop-only{--columnGap:0.25rem}}@media screen and (min-width:1216px){.columns.is-variable.is-1-widescreen{--columnGap:0.25rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-1-widescreen-only{--columnGap:0.25rem}}@media screen and (min-width:1408px){.columns.is-variable.is-1-fullhd{--columnGap:0.25rem}}.columns.is-variable.is-2{--columnGap:0.5rem}@media screen and (max-width:768px){.columns.is-variable.is-2-mobile{--columnGap:0.5rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-2-tablet{--columnGap:0.5rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-2-tablet-only{--columnGap:0.5rem}}@media screen and (max-width:1023px){.columns.is-variable.is-2-touch{--columnGap:0.5rem}}@media screen and (min-width:1024px){.columns.is-variable.is-2-desktop{--columnGap:0.5rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-2-desktop-only{--columnGap:0.5rem}}@media screen and (min-width:1216px){.columns.is-variable.is-2-widescreen{--columnGap:0.5rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-2-widescreen-only{--columnGap:0.5rem}}@media screen and (min-width:1408px){.columns.is-variable.is-2-fullhd{--columnGap:0.5rem}}.columns.is-variable.is-3{--columnGap:0.75rem}@media screen and (max-width:768px){.columns.is-variable.is-3-mobile{--columnGap:0.75rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-3-tablet{--columnGap:0.75rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-3-tablet-only{--columnGap:0.75rem}}@media screen and (max-width:1023px){.columns.is-variable.is-3-touch{--columnGap:0.75rem}}@media screen and (min-width:1024px){.columns.is-variable.is-3-desktop{--columnGap:0.75rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-3-desktop-only{--columnGap:0.75rem}}@media screen and (min-width:1216px){.columns.is-variable.is-3-widescreen{--columnGap:0.75rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-3-widescreen-only{--columnGap:0.75rem}}@media screen and (min-width:1408px){.columns.is-variable.is-3-fullhd{--columnGap:0.75rem}}.columns.is-variable.is-4{--columnGap:1rem}@media screen and (max-width:768px){.columns.is-variable.is-4-mobile{--columnGap:1rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-4-tablet{--columnGap:1rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-4-tablet-only{--columnGap:1rem}}@media screen and (max-width:1023px){.columns.is-variable.is-4-touch{--columnGap:1rem}}@media screen and (min-width:1024px){.columns.is-variable.is-4-desktop{--columnGap:1rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-4-desktop-only{--columnGap:1rem}}@media screen and (min-width:1216px){.columns.is-variable.is-4-widescreen{--columnGap:1rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-4-widescreen-only{--columnGap:1rem}}@media screen and (min-width:1408px){.columns.is-variable.is-4-fullhd{--columnGap:1rem}}.columns.is-variable.is-5{--columnGap:1.25rem}@media screen and (max-width:768px){.columns.is-variable.is-5-mobile{--columnGap:1.25rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-5-tablet{--columnGap:1.25rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-5-tablet-only{--columnGap:1.25rem}}@media screen and (max-width:1023px){.columns.is-variable.is-5-touch{--columnGap:1.25rem}}@media screen and (min-width:1024px){.columns.is-variable.is-5-desktop{--columnGap:1.25rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-5-desktop-only{--columnGap:1.25rem}}@media screen and (min-width:1216px){.columns.is-variable.is-5-widescreen{--columnGap:1.25rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-5-widescreen-only{--columnGap:1.25rem}}@media screen and (min-width:1408px){.columns.is-variable.is-5-fullhd{--columnGap:1.25rem}}.columns.is-variable.is-6{--columnGap:1.5rem}@media screen and (max-width:768px){.columns.is-variable.is-6-mobile{--columnGap:1.5rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-6-tablet{--columnGap:1.5rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-6-tablet-only{--columnGap:1.5rem}}@media screen and (max-width:1023px){.columns.is-variable.is-6-touch{--columnGap:1.5rem}}@media screen and (min-width:1024px){.columns.is-variable.is-6-desktop{--columnGap:1.5rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-6-desktop-only{--columnGap:1.5rem}}@media screen and (min-width:1216px){.columns.is-variable.is-6-widescreen{--columnGap:1.5rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-6-widescreen-only{--columnGap:1.5rem}}@media screen and (min-width:1408px){.columns.is-variable.is-6-fullhd{--columnGap:1.5rem}}.columns.is-variable.is-7{--columnGap:1.75rem}@media screen and (max-width:768px){.columns.is-variable.is-7-mobile{--columnGap:1.75rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-7-tablet{--columnGap:1.75rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-7-tablet-only{--columnGap:1.75rem}}@media screen and (max-width:1023px){.columns.is-variable.is-7-touch{--columnGap:1.75rem}}@media screen and (min-width:1024px){.columns.is-variable.is-7-desktop{--columnGap:1.75rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-7-desktop-only{--columnGap:1.75rem}}@media screen and (min-width:1216px){.columns.is-variable.is-7-widescreen{--columnGap:1.75rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-7-widescreen-only{--columnGap:1.75rem}}@media screen and (min-width:1408px){.columns.is-variable.is-7-fullhd{--columnGap:1.75rem}}.columns.is-variable.is-8{--columnGap:2rem}@media screen and (max-width:768px){.columns.is-variable.is-8-mobile{--columnGap:2rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-8-tablet{--columnGap:2rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-8-tablet-only{--columnGap:2rem}}@media screen and (max-width:1023px){.columns.is-variable.is-8-touch{--columnGap:2rem}}@media screen and (min-width:1024px){.columns.is-variable.is-8-desktop{--columnGap:2rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-8-desktop-only{--columnGap:2rem}}@media screen and (min-width:1216px){.columns.is-variable.is-8-widescreen{--columnGap:2rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-8-widescreen-only{--columnGap:2rem}}@media screen and (min-width:1408px){.columns.is-variable.is-8-fullhd{--columnGap:2rem}}.tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:-webkit-min-content;min-height:-moz-min-content;min-height:min-content}.tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.tile.is-ancestor:last-child{margin-bottom:-.75rem}.tile.is-ancestor:not(:last-child){margin-bottom:.75rem}.tile.is-child{margin:0!important}.tile.is-parent{padding:.75rem}.tile.is-vertical{flex-direction:column}.tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem!important}@media screen and (min-width:769px),print{.tile:not(.is-child){display:flex}.tile.is-1{flex:none;width:8.33333%}.tile.is-2{flex:none;width:16.66667%}.tile.is-3{flex:none;width:25%}.tile.is-4{flex:none;width:33.33333%}.tile.is-5{flex:none;width:41.66667%}.tile.is-6{flex:none;width:50%}.tile.is-7{flex:none;width:58.33333%}.tile.is-8{flex:none;width:66.66667%}.tile.is-9{flex:none;width:75%}.tile.is-10{flex:none;width:83.33333%}.tile.is-11{flex:none;width:91.66667%}.tile.is-12{flex:none;width:100%}}.has-text-white{color:#fff!important}a.has-text-white:focus,a.has-text-white:hover{color:#e6e6e6!important}.has-background-white{background-color:#fff!important}.has-text-black{color:#0a0a0a!important}a.has-text-black:focus,a.has-text-black:hover{color:#000!important}.has-background-black{background-color:#0a0a0a!important}.has-text-light{color:#f5f5f5!important}a.has-text-light:focus,a.has-text-light:hover{color:#dbdbdb!important}.has-background-light{background-color:#f5f5f5!important}.has-text-dark{color:#363636!important}a.has-text-dark:focus,a.has-text-dark:hover{color:#1c1c1c!important}.has-background-dark{background-color:#363636!important}.has-text-primary{color:#00d1b2!important}a.has-text-primary:focus,a.has-text-primary:hover{color:#009e86!important}.has-background-primary{background-color:#00d1b2!important}.has-text-primary-light{color:#ebfffc!important}a.has-text-primary-light:focus,a.has-text-primary-light:hover{color:#b8fff4!important}.has-background-primary-light{background-color:#ebfffc!important}.has-text-primary-dark{color:#00947e!important}a.has-text-primary-dark:focus,a.has-text-primary-dark:hover{color:#00c7a9!important}.has-background-primary-dark{background-color:#00947e!important}.has-text-link{color:#485fc7!important}a.has-text-link:focus,a.has-text-link:hover{color:#3449a8!important}.has-background-link{background-color:#485fc7!important}.has-text-link-light{color:#eff1fa!important}a.has-text-link-light:focus,a.has-text-link-light:hover{color:#c8cfee!important}.has-background-link-light{background-color:#eff1fa!important}.has-text-link-dark{color:#3850b7!important}a.has-text-link-dark:focus,a.has-text-link-dark:hover{color:#576dcb!important}.has-background-link-dark{background-color:#3850b7!important}.has-text-info{color:#3e8ed0!important}a.has-text-info:focus,a.has-text-info:hover{color:#2b74b1!important}.has-background-info{background-color:#3e8ed0!important}.has-text-info-light{color:#eff5fb!important}a.has-text-info-light:focus,a.has-text-info-light:hover{color:#c6ddf1!important}.has-background-info-light{background-color:#eff5fb!important}.has-text-info-dark{color:#296fa8!important}a.has-text-info-dark:focus,a.has-text-info-dark:hover{color:#368ace!important}.has-background-info-dark{background-color:#296fa8!important}.has-text-success{color:#48c78e!important}a.has-text-success:focus,a.has-text-success:hover{color:#34a873!important}.has-background-success{background-color:#48c78e!important}.has-text-success-light{color:#effaf5!important}a.has-text-success-light:focus,a.has-text-success-light:hover{color:#c8eedd!important}.has-background-success-light{background-color:#effaf5!important}.has-text-success-dark{color:#257953!important}a.has-text-success-dark:focus,a.has-text-success-dark:hover{color:#31a06e!important}.has-background-success-dark{background-color:#257953!important}.has-text-warning{color:#ffe08a!important}a.has-text-warning:focus,a.has-text-warning:hover{color:#ffd257!important}.has-background-warning{background-color:#ffe08a!important}.has-text-warning-light{color:#fffaeb!important}a.has-text-warning-light:focus,a.has-text-warning-light:hover{color:#ffecb8!important}.has-background-warning-light{background-color:#fffaeb!important}.has-text-warning-dark{color:#946c00!important}a.has-text-warning-dark:focus,a.has-text-warning-dark:hover{color:#c79200!important}.has-background-warning-dark{background-color:#946c00!important}.has-text-danger{color:#f14668!important}a.has-text-danger:focus,a.has-text-danger:hover{color:#ee1742!important}.has-background-danger{background-color:#f14668!important}.has-text-danger-light{color:#feecf0!important}a.has-text-danger-light:focus,a.has-text-danger-light:hover{color:#fabdc9!important}.has-background-danger-light{background-color:#feecf0!important}.has-text-danger-dark{color:#cc0f35!important}a.has-text-danger-dark:focus,a.has-text-danger-dark:hover{color:#ee2049!important}.has-background-danger-dark{background-color:#cc0f35!important}.has-text-black-bis{color:#121212!important}.has-background-black-bis{background-color:#121212!important}.has-text-black-ter{color:#242424!important}.has-background-black-ter{background-color:#242424!important}.has-text-grey-darker{color:#363636!important}.has-background-grey-darker{background-color:#363636!important}.has-text-grey-dark{color:#4a4a4a!important}.has-background-grey-dark{background-color:#4a4a4a!important}.has-text-grey{color:#7a7a7a!important}.has-background-grey{background-color:#7a7a7a!important}.has-text-grey-light{color:#b5b5b5!important}.has-background-grey-light{background-color:#b5b5b5!important}.has-text-grey-lighter{color:#dbdbdb!important}.has-background-grey-lighter{background-color:#dbdbdb!important}.has-text-white-ter{color:#f5f5f5!important}.has-background-white-ter{background-color:#f5f5f5!important}.has-text-white-bis{color:#fafafa!important}.has-background-white-bis{background-color:#fafafa!important}.is-flex-direction-row{flex-direction:row!important}.is-flex-direction-row-reverse{flex-direction:row-reverse!important}.is-flex-direction-column{flex-direction:column!important}.is-flex-direction-column-reverse{flex-direction:column-reverse!important}.is-flex-wrap-nowrap{flex-wrap:nowrap!important}.is-flex-wrap-wrap{flex-wrap:wrap!important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse!important}.is-justify-content-flex-start{justify-content:flex-start!important}.is-justify-content-flex-end{justify-content:flex-end!important}.is-justify-content-center{justify-content:center!important}.is-justify-content-space-between{justify-content:space-between!important}.is-justify-content-space-around{justify-content:space-around!important}.is-justify-content-space-evenly{justify-content:space-evenly!important}.is-justify-content-start{justify-content:start!important}.is-justify-content-end{justify-content:end!important}.is-justify-content-left{justify-content:left!important}.is-justify-content-right{justify-content:right!important}.is-align-content-flex-start{align-content:flex-start!important}.is-align-content-flex-end{align-content:flex-end!important}.is-align-content-center{align-content:center!important}.is-align-content-space-between{align-content:space-between!important}.is-align-content-space-around{align-content:space-around!important}.is-align-content-space-evenly{align-content:space-evenly!important}.is-align-content-stretch{align-content:stretch!important}.is-align-content-start{align-content:start!important}.is-align-content-end{align-content:end!important}.is-align-content-baseline{align-content:baseline!important}.is-align-items-stretch{align-items:stretch!important}.is-align-items-flex-start{align-items:flex-start!important}.is-align-items-flex-end{align-items:flex-end!important}.is-align-items-center{align-items:center!important}.is-align-items-baseline{align-items:baseline!important}.is-align-items-start{align-items:start!important}.is-align-items-end{align-items:end!important}.is-align-items-self-start{align-items:self-start!important}.is-align-items-self-end{align-items:self-end!important}.is-align-self-auto{align-self:auto!important}.is-align-self-flex-start{align-self:flex-start!important}.is-align-self-flex-end{align-self:flex-end!important}.is-align-self-center{align-self:center!important}.is-align-self-baseline{align-self:baseline!important}.is-align-self-stretch{align-self:stretch!important}.is-flex-grow-0{flex-grow:0!important}.is-flex-grow-1{flex-grow:1!important}.is-flex-grow-2{flex-grow:2!important}.is-flex-grow-3{flex-grow:3!important}.is-flex-grow-4{flex-grow:4!important}.is-flex-grow-5{flex-grow:5!important}.is-flex-shrink-0{flex-shrink:0!important}.is-flex-shrink-1{flex-shrink:1!important}.is-flex-shrink-2{flex-shrink:2!important}.is-flex-shrink-3{flex-shrink:3!important}.is-flex-shrink-4{flex-shrink:4!important}.is-flex-shrink-5{flex-shrink:5!important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left!important}.is-pulled-right{float:right!important}.is-radiusless{border-radius:0!important}.is-shadowless{box-shadow:none!important}.is-clickable{cursor:pointer!important;pointer-events:all!important}.is-clipped{overflow:hidden!important}.is-relative{position:relative!important}.is-marginless{margin:0!important}.is-paddingless{padding:0!important}.m-0{margin:0!important}.mt-0{margin-top:0!important}.mr-0{margin-right:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-top:0!important;margin-bottom:0!important}.m-1{margin:.25rem!important}.mt-1{margin-top:.25rem!important}.mr-1{margin-right:.25rem!important}.mb-1{margin-bottom:.25rem!important}.ml-1{margin-left:.25rem!important}.mx-1{margin-left:.25rem!important;margin-right:.25rem!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.m-2{margin:.5rem!important}.mt-2{margin-top:.5rem!important}.mr-2{margin-right:.5rem!important}.mb-2{margin-bottom:.5rem!important}.ml-2{margin-left:.5rem!important}.mx-2{margin-left:.5rem!important;margin-right:.5rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.m-3{margin:.75rem!important}.mt-3{margin-top:.75rem!important}.mr-3{margin-right:.75rem!important}.mb-3{margin-bottom:.75rem!important}.ml-3{margin-left:.75rem!important}.mx-3{margin-left:.75rem!important;margin-right:.75rem!important}.my-3{margin-top:.75rem!important;margin-bottom:.75rem!important}.m-4{margin:1rem!important}.mt-4{margin-top:1rem!important}.mr-4{margin-right:1rem!important}.mb-4{margin-bottom:1rem!important}.ml-4{margin-left:1rem!important}.mx-4{margin-left:1rem!important;margin-right:1rem!important}.my-4{margin-top:1rem!important;margin-bottom:1rem!important}.m-5{margin:1.5rem!important}.mt-5{margin-top:1.5rem!important}.mr-5{margin-right:1.5rem!important}.mb-5{margin-bottom:1.5rem!important}.ml-5{margin-left:1.5rem!important}.mx-5{margin-left:1.5rem!important;margin-right:1.5rem!important}.my-5{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.m-6{margin:3rem!important}.mt-6{margin-top:3rem!important}.mr-6{margin-right:3rem!important}.mb-6{margin-bottom:3rem!important}.ml-6{margin-left:3rem!important}.mx-6{margin-left:3rem!important;margin-right:3rem!important}.my-6{margin-top:3rem!important;margin-bottom:3rem!important}.m-auto{margin:auto!important}.mt-auto{margin-top:auto!important}.mr-auto{margin-right:auto!important}.mb-auto{margin-bottom:auto!important}.ml-auto{margin-left:auto!important}.mx-auto{margin-left:auto!important;margin-right:auto!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.p-0{padding:0!important}.pt-0{padding-top:0!important}.pr-0{padding-right:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-top:0!important;padding-bottom:0!important}.p-1{padding:.25rem!important}.pt-1{padding-top:.25rem!important}.pr-1{padding-right:.25rem!important}.pb-1{padding-bottom:.25rem!important}.pl-1{padding-left:.25rem!important}.px-1{padding-left:.25rem!important;padding-right:.25rem!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.p-2{padding:.5rem!important}.pt-2{padding-top:.5rem!important}.pr-2{padding-right:.5rem!important}.pb-2{padding-bottom:.5rem!important}.pl-2{padding-left:.5rem!important}.px-2{padding-left:.5rem!important;padding-right:.5rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.p-3{padding:.75rem!important}.pt-3{padding-top:.75rem!important}.pr-3{padding-right:.75rem!important}.pb-3{padding-bottom:.75rem!important}.pl-3{padding-left:.75rem!important}.px-3{padding-left:.75rem!important;padding-right:.75rem!important}.py-3{padding-top:.75rem!important;padding-bottom:.75rem!important}.p-4{padding:1rem!important}.pt-4{padding-top:1rem!important}.pr-4{padding-right:1rem!important}.pb-4{padding-bottom:1rem!important}.pl-4{padding-left:1rem!important}.px-4{padding-left:1rem!important;padding-right:1rem!important}.py-4{padding-top:1rem!important;padding-bottom:1rem!important}.p-5{padding:1.5rem!important}.pt-5{padding-top:1.5rem!important}.pr-5{padding-right:1.5rem!important}.pb-5{padding-bottom:1.5rem!important}.pl-5{padding-left:1.5rem!important}.px-5{padding-left:1.5rem!important;padding-right:1.5rem!important}.py-5{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.p-6{padding:3rem!important}.pt-6{padding-top:3rem!important}.pr-6{padding-right:3rem!important}.pb-6{padding-bottom:3rem!important}.pl-6{padding-left:3rem!important}.px-6{padding-left:3rem!important;padding-right:3rem!important}.py-6{padding-top:3rem!important;padding-bottom:3rem!important}.p-auto{padding:auto!important}.pt-auto{padding-top:auto!important}.pr-auto{padding-right:auto!important}.pb-auto{padding-bottom:auto!important}.pl-auto{padding-left:auto!important}.px-auto{padding-left:auto!important;padding-right:auto!important}.py-auto{padding-top:auto!important;padding-bottom:auto!important}.is-size-1{font-size:3rem!important}.is-size-2{font-size:2.5rem!important}.is-size-3{font-size:2rem!important}.is-size-4{font-size:1.5rem!important}.is-size-5{font-size:1.25rem!important}.is-size-6{font-size:1rem!important}.is-size-7{font-size:.75rem!important}@media screen and (max-width:768px){.is-size-1-mobile{font-size:3rem!important}.is-size-2-mobile{font-size:2.5rem!important}.is-size-3-mobile{font-size:2rem!important}.is-size-4-mobile{font-size:1.5rem!important}.is-size-5-mobile{font-size:1.25rem!important}.is-size-6-mobile{font-size:1rem!important}.is-size-7-mobile{font-size:.75rem!important}}@media screen and (min-width:769px),print{.is-size-1-tablet{font-size:3rem!important}.is-size-2-tablet{font-size:2.5rem!important}.is-size-3-tablet{font-size:2rem!important}.is-size-4-tablet{font-size:1.5rem!important}.is-size-5-tablet{font-size:1.25rem!important}.is-size-6-tablet{font-size:1rem!important}.is-size-7-tablet{font-size:.75rem!important}}@media screen and (max-width:1023px){.is-size-1-touch{font-size:3rem!important}.is-size-2-touch{font-size:2.5rem!important}.is-size-3-touch{font-size:2rem!important}.is-size-4-touch{font-size:1.5rem!important}.is-size-5-touch{font-size:1.25rem!important}.is-size-6-touch{font-size:1rem!important}.is-size-7-touch{font-size:.75rem!important}}@media screen and (min-width:1024px){.is-size-1-desktop{font-size:3rem!important}.is-size-2-desktop{font-size:2.5rem!important}.is-size-3-desktop{font-size:2rem!important}.is-size-4-desktop{font-size:1.5rem!important}.is-size-5-desktop{font-size:1.25rem!important}.is-size-6-desktop{font-size:1rem!important}.is-size-7-desktop{font-size:.75rem!important}}@media screen and (min-width:1216px){.is-size-1-widescreen{font-size:3rem!important}.is-size-2-widescreen{font-size:2.5rem!important}.is-size-3-widescreen{font-size:2rem!important}.is-size-4-widescreen{font-size:1.5rem!important}.is-size-5-widescreen{font-size:1.25rem!important}.is-size-6-widescreen{font-size:1rem!important}.is-size-7-widescreen{font-size:.75rem!important}}@media screen and (min-width:1408px){.is-size-1-fullhd{font-size:3rem!important}.is-size-2-fullhd{font-size:2.5rem!important}.is-size-3-fullhd{font-size:2rem!important}.is-size-4-fullhd{font-size:1.5rem!important}.is-size-5-fullhd{font-size:1.25rem!important}.is-size-6-fullhd{font-size:1rem!important}.is-size-7-fullhd{font-size:.75rem!important}}.has-text-centered{text-align:center!important}.has-text-justified{text-align:justify!important}.has-text-left{text-align:left!important}.has-text-right{text-align:right!important}@media screen and (max-width:768px){.has-text-centered-mobile{text-align:center!important}}@media screen and (min-width:769px),print{.has-text-centered-tablet{text-align:center!important}}@media screen and (min-width:769px) and (max-width:1023px){.has-text-centered-tablet-only{text-align:center!important}}@media screen and (max-width:1023px){.has-text-centered-touch{text-align:center!important}}@media screen and (min-width:1024px){.has-text-centered-desktop{text-align:center!important}}@media screen and (min-width:1024px) and (max-width:1215px){.has-text-centered-desktop-only{text-align:center!important}}@media screen and (min-width:1216px){.has-text-centered-widescreen{text-align:center!important}}@media screen and (min-width:1216px) and (max-width:1407px){.has-text-centered-widescreen-only{text-align:center!important}}@media screen and (min-width:1408px){.has-text-centered-fullhd{text-align:center!important}}@media screen and (max-width:768px){.has-text-justified-mobile{text-align:justify!important}}@media screen and (min-width:769px),print{.has-text-justified-tablet{text-align:justify!important}}@media screen and (min-width:769px) and (max-width:1023px){.has-text-justified-tablet-only{text-align:justify!important}}@media screen and (max-width:1023px){.has-text-justified-touch{text-align:justify!important}}@media screen and (min-width:1024px){.has-text-justified-desktop{text-align:justify!important}}@media screen and (min-width:1024px) and (max-width:1215px){.has-text-justified-desktop-only{text-align:justify!important}}@media screen and (min-width:1216px){.has-text-justified-widescreen{text-align:justify!important}}@media screen and (min-width:1216px) and (max-width:1407px){.has-text-justified-widescreen-only{text-align:justify!important}}@media screen and (min-width:1408px){.has-text-justified-fullhd{text-align:justify!important}}@media screen and (max-width:768px){.has-text-left-mobile{text-align:left!important}}@media screen and (min-width:769px),print{.has-text-left-tablet{text-align:left!important}}@media screen and (min-width:769px) and (max-width:1023px){.has-text-left-tablet-only{text-align:left!important}}@media screen and (max-width:1023px){.has-text-left-touch{text-align:left!important}}@media screen and (min-width:1024px){.has-text-left-desktop{text-align:left!important}}@media screen and (min-width:1024px) and (max-width:1215px){.has-text-left-desktop-only{text-align:left!important}}@media screen and (min-width:1216px){.has-text-left-widescreen{text-align:left!important}}@media screen and (min-width:1216px) and (max-width:1407px){.has-text-left-widescreen-only{text-align:left!important}}@media screen and (min-width:1408px){.has-text-left-fullhd{text-align:left!important}}@media screen and (max-width:768px){.has-text-right-mobile{text-align:right!important}}@media screen and (min-width:769px),print{.has-text-right-tablet{text-align:right!important}}@media screen and (min-width:769px) and (max-width:1023px){.has-text-right-tablet-only{text-align:right!important}}@media screen and (max-width:1023px){.has-text-right-touch{text-align:right!important}}@media screen and (min-width:1024px){.has-text-right-desktop{text-align:right!important}}@media screen and (min-width:1024px) and (max-width:1215px){.has-text-right-desktop-only{text-align:right!important}}@media screen and (min-width:1216px){.has-text-right-widescreen{text-align:right!important}}@media screen and (min-width:1216px) and (max-width:1407px){.has-text-right-widescreen-only{text-align:right!important}}@media screen and (min-width:1408px){.has-text-right-fullhd{text-align:right!important}}.is-capitalized{text-transform:capitalize!important}.is-lowercase{text-transform:lowercase!important}.is-uppercase{text-transform:uppercase!important}.is-italic{font-style:italic!important}.is-underlined{text-decoration:underline!important}.has-text-weight-light{font-weight:300!important}.has-text-weight-normal{font-weight:400!important}.has-text-weight-medium{font-weight:500!important}.has-text-weight-semibold{font-weight:600!important}.has-text-weight-bold{font-weight:700!important}.is-family-primary{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif!important}.is-family-secondary{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif!important}.is-family-sans-serif{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif!important}.is-family-monospace{font-family:monospace!important}.is-family-code{font-family:monospace!important}.is-block{display:block!important}@media screen and (max-width:768px){.is-block-mobile{display:block!important}}@media screen and (min-width:769px),print{.is-block-tablet{display:block!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-block-tablet-only{display:block!important}}@media screen and (max-width:1023px){.is-block-touch{display:block!important}}@media screen and (min-width:1024px){.is-block-desktop{display:block!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-block-desktop-only{display:block!important}}@media screen and (min-width:1216px){.is-block-widescreen{display:block!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-block-widescreen-only{display:block!important}}@media screen and (min-width:1408px){.is-block-fullhd{display:block!important}}.is-flex{display:flex!important}@media screen and (max-width:768px){.is-flex-mobile{display:flex!important}}@media screen and (min-width:769px),print{.is-flex-tablet{display:flex!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-flex-tablet-only{display:flex!important}}@media screen and (max-width:1023px){.is-flex-touch{display:flex!important}}@media screen and (min-width:1024px){.is-flex-desktop{display:flex!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-flex-desktop-only{display:flex!important}}@media screen and (min-width:1216px){.is-flex-widescreen{display:flex!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-flex-widescreen-only{display:flex!important}}@media screen and (min-width:1408px){.is-flex-fullhd{display:flex!important}}.is-inline{display:inline!important}@media screen and (max-width:768px){.is-inline-mobile{display:inline!important}}@media screen and (min-width:769px),print{.is-inline-tablet{display:inline!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-inline-tablet-only{display:inline!important}}@media screen and (max-width:1023px){.is-inline-touch{display:inline!important}}@media screen and (min-width:1024px){.is-inline-desktop{display:inline!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-inline-desktop-only{display:inline!important}}@media screen and (min-width:1216px){.is-inline-widescreen{display:inline!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-inline-widescreen-only{display:inline!important}}@media screen and (min-width:1408px){.is-inline-fullhd{display:inline!important}}.is-inline-block{display:inline-block!important}@media screen and (max-width:768px){.is-inline-block-mobile{display:inline-block!important}}@media screen and (min-width:769px),print{.is-inline-block-tablet{display:inline-block!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-inline-block-tablet-only{display:inline-block!important}}@media screen and (max-width:1023px){.is-inline-block-touch{display:inline-block!important}}@media screen and (min-width:1024px){.is-inline-block-desktop{display:inline-block!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-inline-block-desktop-only{display:inline-block!important}}@media screen and (min-width:1216px){.is-inline-block-widescreen{display:inline-block!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-inline-block-widescreen-only{display:inline-block!important}}@media screen and (min-width:1408px){.is-inline-block-fullhd{display:inline-block!important}}.is-inline-flex{display:inline-flex!important}@media screen and (max-width:768px){.is-inline-flex-mobile{display:inline-flex!important}}@media screen and (min-width:769px),print{.is-inline-flex-tablet{display:inline-flex!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-inline-flex-tablet-only{display:inline-flex!important}}@media screen and (max-width:1023px){.is-inline-flex-touch{display:inline-flex!important}}@media screen and (min-width:1024px){.is-inline-flex-desktop{display:inline-flex!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-inline-flex-desktop-only{display:inline-flex!important}}@media screen and (min-width:1216px){.is-inline-flex-widescreen{display:inline-flex!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-inline-flex-widescreen-only{display:inline-flex!important}}@media screen and (min-width:1408px){.is-inline-flex-fullhd{display:inline-flex!important}}.is-hidden{display:none!important}.is-sr-only{border:none!important;clip:rect(0,0,0,0)!important;height:.01em!important;overflow:hidden!important;padding:0!important;position:absolute!important;white-space:nowrap!important;width:.01em!important}@media screen and (max-width:768px){.is-hidden-mobile{display:none!important}}@media screen and (min-width:769px),print{.is-hidden-tablet{display:none!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-hidden-tablet-only{display:none!important}}@media screen and (max-width:1023px){.is-hidden-touch{display:none!important}}@media screen and (min-width:1024px){.is-hidden-desktop{display:none!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-hidden-desktop-only{display:none!important}}@media screen and (min-width:1216px){.is-hidden-widescreen{display:none!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-hidden-widescreen-only{display:none!important}}@media screen and (min-width:1408px){.is-hidden-fullhd{display:none!important}}.is-invisible{visibility:hidden!important}@media screen and (max-width:768px){.is-invisible-mobile{visibility:hidden!important}}@media screen and (min-width:769px),print{.is-invisible-tablet{visibility:hidden!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-invisible-tablet-only{visibility:hidden!important}}@media screen and (max-width:1023px){.is-invisible-touch{visibility:hidden!important}}@media screen and (min-width:1024px){.is-invisible-desktop{visibility:hidden!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-invisible-desktop-only{visibility:hidden!important}}@media screen and (min-width:1216px){.is-invisible-widescreen{visibility:hidden!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-invisible-widescreen-only{visibility:hidden!important}}@media screen and (min-width:1408px){.is-invisible-fullhd{visibility:hidden!important}}.hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}.hero .navbar{background:0 0}.hero .tabs ul{border-bottom:none}.hero.is-white{background-color:#fff;color:#0a0a0a}.hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-white strong{color:inherit}.hero.is-white .title{color:#0a0a0a}.hero.is-white .subtitle{color:rgba(10,10,10,.9)}.hero.is-white .subtitle a:not(.button),.hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width:1023px){.hero.is-white .navbar-menu{background-color:#fff}}.hero.is-white .navbar-item,.hero.is-white .navbar-link{color:rgba(10,10,10,.7)}.hero.is-white .navbar-link.is-active,.hero.is-white .navbar-link:hover,.hero.is-white a.navbar-item.is-active,.hero.is-white a.navbar-item:hover{background-color:#f2f2f2;color:#0a0a0a}.hero.is-white .tabs a{color:#0a0a0a;opacity:.9}.hero.is-white .tabs a:hover{opacity:1}.hero.is-white .tabs li.is-active a{color:#fff!important;opacity:1}.hero.is-white .tabs.is-boxed a,.hero.is-white .tabs.is-toggle a{color:#0a0a0a}.hero.is-white .tabs.is-boxed a:hover,.hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-white .tabs.is-boxed li.is-active a,.hero.is-white .tabs.is-boxed li.is-active a:hover,.hero.is-white .tabs.is-toggle li.is-active a,.hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.hero.is-white.is-bold{background-image:linear-gradient(141deg,#e6e6e6 0,#fff 71%,#fff 100%)}@media screen and (max-width:768px){.hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg,#e6e6e6 0,#fff 71%,#fff 100%)}}.hero.is-black{background-color:#0a0a0a;color:#fff}.hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-black strong{color:inherit}.hero.is-black .title{color:#fff}.hero.is-black .subtitle{color:rgba(255,255,255,.9)}.hero.is-black .subtitle a:not(.button),.hero.is-black .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-black .navbar-menu{background-color:#0a0a0a}}.hero.is-black .navbar-item,.hero.is-black .navbar-link{color:rgba(255,255,255,.7)}.hero.is-black .navbar-link.is-active,.hero.is-black .navbar-link:hover,.hero.is-black a.navbar-item.is-active,.hero.is-black a.navbar-item:hover{background-color:#000;color:#fff}.hero.is-black .tabs a{color:#fff;opacity:.9}.hero.is-black .tabs a:hover{opacity:1}.hero.is-black .tabs li.is-active a{color:#0a0a0a!important;opacity:1}.hero.is-black .tabs.is-boxed a,.hero.is-black .tabs.is-toggle a{color:#fff}.hero.is-black .tabs.is-boxed a:hover,.hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-black .tabs.is-boxed li.is-active a,.hero.is-black .tabs.is-boxed li.is-active a:hover,.hero.is-black .tabs.is-toggle li.is-active a,.hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}.hero.is-black.is-bold{background-image:linear-gradient(141deg,#000 0,#0a0a0a 71%,#181616 100%)}@media screen and (max-width:768px){.hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg,#000 0,#0a0a0a 71%,#181616 100%)}}.hero.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-light strong{color:inherit}.hero.is-light .title{color:rgba(0,0,0,.7)}.hero.is-light .subtitle{color:rgba(0,0,0,.9)}.hero.is-light .subtitle a:not(.button),.hero.is-light .subtitle strong{color:rgba(0,0,0,.7)}@media screen and (max-width:1023px){.hero.is-light .navbar-menu{background-color:#f5f5f5}}.hero.is-light .navbar-item,.hero.is-light .navbar-link{color:rgba(0,0,0,.7)}.hero.is-light .navbar-link.is-active,.hero.is-light .navbar-link:hover,.hero.is-light a.navbar-item.is-active,.hero.is-light a.navbar-item:hover{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.hero.is-light .tabs a{color:rgba(0,0,0,.7);opacity:.9}.hero.is-light .tabs a:hover{opacity:1}.hero.is-light .tabs li.is-active a{color:#f5f5f5!important;opacity:1}.hero.is-light .tabs.is-boxed a,.hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,.7)}.hero.is-light .tabs.is-boxed a:hover,.hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-light .tabs.is-boxed li.is-active a,.hero.is-light .tabs.is-boxed li.is-active a:hover,.hero.is-light .tabs.is-toggle li.is-active a,.hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,.7);border-color:rgba(0,0,0,.7);color:#f5f5f5}.hero.is-light.is-bold{background-image:linear-gradient(141deg,#dfd8d9 0,#f5f5f5 71%,#fff 100%)}@media screen and (max-width:768px){.hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg,#dfd8d9 0,#f5f5f5 71%,#fff 100%)}}.hero.is-dark{background-color:#363636;color:#fff}.hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-dark strong{color:inherit}.hero.is-dark .title{color:#fff}.hero.is-dark .subtitle{color:rgba(255,255,255,.9)}.hero.is-dark .subtitle a:not(.button),.hero.is-dark .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-dark .navbar-menu{background-color:#363636}}.hero.is-dark .navbar-item,.hero.is-dark .navbar-link{color:rgba(255,255,255,.7)}.hero.is-dark .navbar-link.is-active,.hero.is-dark .navbar-link:hover,.hero.is-dark a.navbar-item.is-active,.hero.is-dark a.navbar-item:hover{background-color:#292929;color:#fff}.hero.is-dark .tabs a{color:#fff;opacity:.9}.hero.is-dark .tabs a:hover{opacity:1}.hero.is-dark .tabs li.is-active a{color:#363636!important;opacity:1}.hero.is-dark .tabs.is-boxed a,.hero.is-dark .tabs.is-toggle a{color:#fff}.hero.is-dark .tabs.is-boxed a:hover,.hero.is-dark .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-dark .tabs.is-boxed li.is-active a,.hero.is-dark .tabs.is-boxed li.is-active a:hover,.hero.is-dark .tabs.is-toggle li.is-active a,.hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#363636}.hero.is-dark.is-bold{background-image:linear-gradient(141deg,#1f191a 0,#363636 71%,#46403f 100%)}@media screen and (max-width:768px){.hero.is-dark.is-bold .navbar-menu{background-image:linear-gradient(141deg,#1f191a 0,#363636 71%,#46403f 100%)}}.hero.is-primary{background-color:#00d1b2;color:#fff}.hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-primary strong{color:inherit}.hero.is-primary .title{color:#fff}.hero.is-primary .subtitle{color:rgba(255,255,255,.9)}.hero.is-primary .subtitle a:not(.button),.hero.is-primary .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-primary .navbar-menu{background-color:#00d1b2}}.hero.is-primary .navbar-item,.hero.is-primary .navbar-link{color:rgba(255,255,255,.7)}.hero.is-primary .navbar-link.is-active,.hero.is-primary .navbar-link:hover,.hero.is-primary a.navbar-item.is-active,.hero.is-primary a.navbar-item:hover{background-color:#00b89c;color:#fff}.hero.is-primary .tabs a{color:#fff;opacity:.9}.hero.is-primary .tabs a:hover{opacity:1}.hero.is-primary .tabs li.is-active a{color:#00d1b2!important;opacity:1}.hero.is-primary .tabs.is-boxed a,.hero.is-primary .tabs.is-toggle a{color:#fff}.hero.is-primary .tabs.is-boxed a:hover,.hero.is-primary .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-primary .tabs.is-boxed li.is-active a,.hero.is-primary .tabs.is-boxed li.is-active a:hover,.hero.is-primary .tabs.is-toggle li.is-active a,.hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#00d1b2}.hero.is-primary.is-bold{background-image:linear-gradient(141deg,#009e6c 0,#00d1b2 71%,#00e7eb 100%)}@media screen and (max-width:768px){.hero.is-primary.is-bold .navbar-menu{background-image:linear-gradient(141deg,#009e6c 0,#00d1b2 71%,#00e7eb 100%)}}.hero.is-link{background-color:#485fc7;color:#fff}.hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-link strong{color:inherit}.hero.is-link .title{color:#fff}.hero.is-link .subtitle{color:rgba(255,255,255,.9)}.hero.is-link .subtitle a:not(.button),.hero.is-link .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-link .navbar-menu{background-color:#485fc7}}.hero.is-link .navbar-item,.hero.is-link .navbar-link{color:rgba(255,255,255,.7)}.hero.is-link .navbar-link.is-active,.hero.is-link .navbar-link:hover,.hero.is-link a.navbar-item.is-active,.hero.is-link a.navbar-item:hover{background-color:#3a51bb;color:#fff}.hero.is-link .tabs a{color:#fff;opacity:.9}.hero.is-link .tabs a:hover{opacity:1}.hero.is-link .tabs li.is-active a{color:#485fc7!important;opacity:1}.hero.is-link .tabs.is-boxed a,.hero.is-link .tabs.is-toggle a{color:#fff}.hero.is-link .tabs.is-boxed a:hover,.hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-link .tabs.is-boxed li.is-active a,.hero.is-link .tabs.is-boxed li.is-active a:hover,.hero.is-link .tabs.is-toggle li.is-active a,.hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#485fc7}.hero.is-link.is-bold{background-image:linear-gradient(141deg,#2959b3 0,#485fc7 71%,#5658d2 100%)}@media screen and (max-width:768px){.hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg,#2959b3 0,#485fc7 71%,#5658d2 100%)}}.hero.is-info{background-color:#3e8ed0;color:#fff}.hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-info strong{color:inherit}.hero.is-info .title{color:#fff}.hero.is-info .subtitle{color:rgba(255,255,255,.9)}.hero.is-info .subtitle a:not(.button),.hero.is-info .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-info .navbar-menu{background-color:#3e8ed0}}.hero.is-info .navbar-item,.hero.is-info .navbar-link{color:rgba(255,255,255,.7)}.hero.is-info .navbar-link.is-active,.hero.is-info .navbar-link:hover,.hero.is-info a.navbar-item.is-active,.hero.is-info a.navbar-item:hover{background-color:#3082c5;color:#fff}.hero.is-info .tabs a{color:#fff;opacity:.9}.hero.is-info .tabs a:hover{opacity:1}.hero.is-info .tabs li.is-active a{color:#3e8ed0!important;opacity:1}.hero.is-info .tabs.is-boxed a,.hero.is-info .tabs.is-toggle a{color:#fff}.hero.is-info .tabs.is-boxed a:hover,.hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-info .tabs.is-boxed li.is-active a,.hero.is-info .tabs.is-boxed li.is-active a:hover,.hero.is-info .tabs.is-toggle li.is-active a,.hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#3e8ed0}.hero.is-info.is-bold{background-image:linear-gradient(141deg,#208fbc 0,#3e8ed0 71%,#4d83db 100%)}@media screen and (max-width:768px){.hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg,#208fbc 0,#3e8ed0 71%,#4d83db 100%)}}.hero.is-success{background-color:#48c78e;color:#fff}.hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-success strong{color:inherit}.hero.is-success .title{color:#fff}.hero.is-success .subtitle{color:rgba(255,255,255,.9)}.hero.is-success .subtitle a:not(.button),.hero.is-success .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-success .navbar-menu{background-color:#48c78e}}.hero.is-success .navbar-item,.hero.is-success .navbar-link{color:rgba(255,255,255,.7)}.hero.is-success .navbar-link.is-active,.hero.is-success .navbar-link:hover,.hero.is-success a.navbar-item.is-active,.hero.is-success a.navbar-item:hover{background-color:#3abb81;color:#fff}.hero.is-success .tabs a{color:#fff;opacity:.9}.hero.is-success .tabs a:hover{opacity:1}.hero.is-success .tabs li.is-active a{color:#48c78e!important;opacity:1}.hero.is-success .tabs.is-boxed a,.hero.is-success .tabs.is-toggle a{color:#fff}.hero.is-success .tabs.is-boxed a:hover,.hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-success .tabs.is-boxed li.is-active a,.hero.is-success .tabs.is-boxed li.is-active a:hover,.hero.is-success .tabs.is-toggle li.is-active a,.hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#48c78e}.hero.is-success.is-bold{background-image:linear-gradient(141deg,#29b35e 0,#48c78e 71%,#56d2af 100%)}@media screen and (max-width:768px){.hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg,#29b35e 0,#48c78e 71%,#56d2af 100%)}}.hero.is-warning{background-color:#ffe08a;color:rgba(0,0,0,.7)}.hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-warning strong{color:inherit}.hero.is-warning .title{color:rgba(0,0,0,.7)}.hero.is-warning .subtitle{color:rgba(0,0,0,.9)}.hero.is-warning .subtitle a:not(.button),.hero.is-warning .subtitle strong{color:rgba(0,0,0,.7)}@media screen and (max-width:1023px){.hero.is-warning .navbar-menu{background-color:#ffe08a}}.hero.is-warning .navbar-item,.hero.is-warning .navbar-link{color:rgba(0,0,0,.7)}.hero.is-warning .navbar-link.is-active,.hero.is-warning .navbar-link:hover,.hero.is-warning a.navbar-item.is-active,.hero.is-warning a.navbar-item:hover{background-color:#ffd970;color:rgba(0,0,0,.7)}.hero.is-warning .tabs a{color:rgba(0,0,0,.7);opacity:.9}.hero.is-warning .tabs a:hover{opacity:1}.hero.is-warning .tabs li.is-active a{color:#ffe08a!important;opacity:1}.hero.is-warning .tabs.is-boxed a,.hero.is-warning .tabs.is-toggle a{color:rgba(0,0,0,.7)}.hero.is-warning .tabs.is-boxed a:hover,.hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-warning .tabs.is-boxed li.is-active a,.hero.is-warning .tabs.is-boxed li.is-active a:hover,.hero.is-warning .tabs.is-toggle li.is-active a,.hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,.7);border-color:rgba(0,0,0,.7);color:#ffe08a}.hero.is-warning.is-bold{background-image:linear-gradient(141deg,#ffb657 0,#ffe08a 71%,#fff6a3 100%)}@media screen and (max-width:768px){.hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg,#ffb657 0,#ffe08a 71%,#fff6a3 100%)}}.hero.is-danger{background-color:#f14668;color:#fff}.hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-danger strong{color:inherit}.hero.is-danger .title{color:#fff}.hero.is-danger .subtitle{color:rgba(255,255,255,.9)}.hero.is-danger .subtitle a:not(.button),.hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-danger .navbar-menu{background-color:#f14668}}.hero.is-danger .navbar-item,.hero.is-danger .navbar-link{color:rgba(255,255,255,.7)}.hero.is-danger .navbar-link.is-active,.hero.is-danger .navbar-link:hover,.hero.is-danger a.navbar-item.is-active,.hero.is-danger a.navbar-item:hover{background-color:#ef2e55;color:#fff}.hero.is-danger .tabs a{color:#fff;opacity:.9}.hero.is-danger .tabs a:hover{opacity:1}.hero.is-danger .tabs li.is-active a{color:#f14668!important;opacity:1}.hero.is-danger .tabs.is-boxed a,.hero.is-danger .tabs.is-toggle a{color:#fff}.hero.is-danger .tabs.is-boxed a:hover,.hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-danger .tabs.is-boxed li.is-active a,.hero.is-danger .tabs.is-boxed li.is-active a:hover,.hero.is-danger .tabs.is-toggle li.is-active a,.hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#f14668}.hero.is-danger.is-bold{background-image:linear-gradient(141deg,#fa0a62 0,#f14668 71%,#f7595f 100%)}@media screen and (max-width:768px){.hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg,#fa0a62 0,#f14668 71%,#f7595f 100%)}}.hero.is-small .hero-body{padding:1.5rem}@media screen and (min-width:769px),print{.hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width:769px),print{.hero.is-large .hero-body{padding:18rem 6rem}}.hero.is-fullheight .hero-body,.hero.is-fullheight-with-navbar .hero-body,.hero.is-halfheight .hero-body{align-items:center;display:flex}.hero.is-fullheight .hero-body>.container,.hero.is-fullheight-with-navbar .hero-body>.container,.hero.is-halfheight .hero-body>.container{flex-grow:1;flex-shrink:1}.hero.is-halfheight{min-height:50vh}.hero.is-fullheight{min-height:100vh}.hero-video{overflow:hidden}.hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%,-50%,0)}.hero-video.is-transparent{opacity:.3}@media screen and (max-width:768px){.hero-video{display:none}}.hero-buttons{margin-top:1.5rem}@media screen and (max-width:768px){.hero-buttons .button{display:flex}.hero-buttons .button:not(:last-child){margin-bottom:.75rem}}@media screen and (min-width:769px),print{.hero-buttons{display:flex;justify-content:center}.hero-buttons .button:not(:last-child){margin-right:1.5rem}}.hero-foot,.hero-head{flex-grow:0;flex-shrink:0}.hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width:769px),print{.hero-body{padding:3rem 3rem}}.section{padding:3rem 1.5rem}@media screen and (min-width:1024px){.section{padding:3rem 3rem}.section.is-medium{padding:9rem 4.5rem}.section.is-large{padding:18rem 6rem}}.footer{background-color:#fafafa;padding:3rem 1.5rem 6rem}
+|]
+
+-- | To avoid run-time dependency from the static content, embed Bulma Tooltip extension in the page's header.
+bulmaTooltipCSS :: String
+bulmaTooltipCSS = [s|
+/*! @creativebulma/bulma-tooltip v1.2.0 | (c) 2020 Gaetan | MIT License | https://github.com/CreativeBulma/bulma-tooltip */
+[data-tooltip]:not(.is-disabled),[data-tooltip]:not(.is-loading),[data-tooltip]:not([disabled]){cursor:pointer;overflow:visible;position:relative}[data-tooltip]:not(.is-disabled):before,[data-tooltip]:not(.is-loading):before,[data-tooltip]:not([disabled]):before{background:rgba(74,74,74,.9);border-radius:2px;content:attr(data-tooltip);padding:.5rem 1rem;text-overflow:ellipsis;white-space:pre-line;right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}[data-tooltip]:not(.is-disabled).has-tooltip-arrow:after,[data-tooltip]:not(.is-disabled):before,[data-tooltip]:not(.is-loading).has-tooltip-arrow:after,[data-tooltip]:not(.is-loading):before,[data-tooltip]:not([disabled]).has-tooltip-arrow:after,[data-tooltip]:not([disabled]):before{box-sizing:border-box;color:#fff;display:inline-block;font-family:BlinkMacSystemFont,-apple-system,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,Helvetica,Arial,sans-serif;font-size:.75rem;-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;opacity:0;overflow:hidden;pointer-events:none;position:absolute;visibility:hidden;z-index:1}[data-tooltip]:not(.is-disabled).has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-arrow:after{content:"";border-style:solid;border-width:6px;border-color:rgba(74,74,74,.9) transparent transparent;margin-bottom:-5px}[data-tooltip]:not(.is-disabled).has-tooltip-arrow.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-arrow.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-arrow.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-bottom.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom:before,[data-tooltip]:not([disabled]).has-tooltip-bottom:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}[data-tooltip]:not(.is-disabled).has-tooltip-left.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left:before,[data-tooltip]:not(.is-loading).has-tooltip-left:before,[data-tooltip]:not([disabled]).has-tooltip-left:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}[data-tooltip]:not(.is-disabled).has-tooltip-right.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right:before,[data-tooltip]:not(.is-loading).has-tooltip-right:before,[data-tooltip]:not([disabled]).has-tooltip-right:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}[data-tooltip]:not(.is-disabled).has-tooltip-multiline:before,[data-tooltip]:not(.is-loading).has-tooltip-multiline:before,[data-tooltip]:not([disabled]).has-tooltip-multiline:before{height:auto;width:15rem;max-width:15rem;text-overflow:clip;white-space:normal;word-break:keep-all}[data-tooltip]:not(.is-disabled).has-tooltip-text-left:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left:before,[data-tooltip]:not([disabled]).has-tooltip-text-left:before{text-align:left}[data-tooltip]:not(.is-disabled).has-tooltip-text-centered:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered:before{text-align:center}[data-tooltip]:not(.is-disabled).has-tooltip-text-right:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right:before,[data-tooltip]:not([disabled]).has-tooltip-text-right:before{text-align:right}[data-tooltip]:not(.is-disabled).has-tooltip-white:after,[data-tooltip]:not(.is-loading).has-tooltip-white:after,[data-tooltip]:not([disabled]).has-tooltip-white:after{border-color:hsla(0,0%,100%,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-white.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-white.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-white.has-tooltip-bottom:after{border-color:transparent transparent hsla(0,0%,100%,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-white.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-white.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-white.has-tooltip-left:after{border-color:transparent transparent transparent hsla(0,0%,100%,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-white.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-white.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-white.has-tooltip-right:after{border-color:transparent hsla(0,0%,100%,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-white:before,[data-tooltip]:not(.is-loading).has-tooltip-white:before,[data-tooltip]:not([disabled]).has-tooltip-white:before{background-color:hsla(0,0%,100%,.9);color:#0a0a0a}[data-tooltip]:not(.is-disabled).has-tooltip-black:after,[data-tooltip]:not(.is-loading).has-tooltip-black:after,[data-tooltip]:not([disabled]).has-tooltip-black:after{border-color:hsla(0,0%,4%,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-black.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-black.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-black.has-tooltip-bottom:after{border-color:transparent transparent hsla(0,0%,4%,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-black.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-black.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-black.has-tooltip-left:after{border-color:transparent transparent transparent hsla(0,0%,4%,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-black.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-black.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-black.has-tooltip-right:after{border-color:transparent hsla(0,0%,4%,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-black:before,[data-tooltip]:not(.is-loading).has-tooltip-black:before,[data-tooltip]:not([disabled]).has-tooltip-black:before{background-color:hsla(0,0%,4%,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-light:after,[data-tooltip]:not(.is-loading).has-tooltip-light:after,[data-tooltip]:not([disabled]).has-tooltip-light:after{border-color:hsla(0,0%,96%,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-light.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-light.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-light.has-tooltip-bottom:after{border-color:transparent transparent hsla(0,0%,96%,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-light.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-light.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-light.has-tooltip-left:after{border-color:transparent transparent transparent hsla(0,0%,96%,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-light.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-light.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-light.has-tooltip-right:after{border-color:transparent hsla(0,0%,96%,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-light:before,[data-tooltip]:not(.is-loading).has-tooltip-light:before,[data-tooltip]:not([disabled]).has-tooltip-light:before{background-color:hsla(0,0%,96%,.9);color:rgba(0,0,0,.7)}[data-tooltip]:not(.is-disabled).has-tooltip-dark:after,[data-tooltip]:not(.is-loading).has-tooltip-dark:after,[data-tooltip]:not([disabled]).has-tooltip-dark:after{border-color:rgba(54,54,54,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-dark.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-dark.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-dark.has-tooltip-bottom:after{border-color:transparent transparent rgba(54,54,54,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-dark.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-dark.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-dark.has-tooltip-left:after{border-color:transparent transparent transparent rgba(54,54,54,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-dark.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-dark.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-dark.has-tooltip-right:after{border-color:transparent rgba(54,54,54,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-dark:before,[data-tooltip]:not(.is-loading).has-tooltip-dark:before,[data-tooltip]:not([disabled]).has-tooltip-dark:before{background-color:rgba(54,54,54,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-primary:after,[data-tooltip]:not(.is-loading).has-tooltip-primary:after,[data-tooltip]:not([disabled]).has-tooltip-primary:after{border-color:rgba(0,209,178,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-primary.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-primary.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-primary.has-tooltip-bottom:after{border-color:transparent transparent rgba(0,209,178,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-primary.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-primary.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-primary.has-tooltip-left:after{border-color:transparent transparent transparent rgba(0,209,178,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-primary.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-primary.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-primary.has-tooltip-right:after{border-color:transparent rgba(0,209,178,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-primary:before,[data-tooltip]:not(.is-loading).has-tooltip-primary:before,[data-tooltip]:not([disabled]).has-tooltip-primary:before{background-color:rgba(0,209,178,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-link:after,[data-tooltip]:not(.is-loading).has-tooltip-link:after,[data-tooltip]:not([disabled]).has-tooltip-link:after{border-color:rgba(50,115,220,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-link.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-link.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-link.has-tooltip-bottom:after{border-color:transparent transparent rgba(50,115,220,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-link.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-link.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-link.has-tooltip-left:after{border-color:transparent transparent transparent rgba(50,115,220,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-link.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-link.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-link.has-tooltip-right:after{border-color:transparent rgba(50,115,220,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-link:before,[data-tooltip]:not(.is-loading).has-tooltip-link:before,[data-tooltip]:not([disabled]).has-tooltip-link:before{background-color:rgba(50,115,220,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-info:after,[data-tooltip]:not(.is-loading).has-tooltip-info:after,[data-tooltip]:not([disabled]).has-tooltip-info:after{border-color:rgba(50,152,220,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-info.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-info.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-info.has-tooltip-bottom:after{border-color:transparent transparent rgba(50,152,220,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-info.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-info.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-info.has-tooltip-left:after{border-color:transparent transparent transparent rgba(50,152,220,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-info.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-info.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-info.has-tooltip-right:after{border-color:transparent rgba(50,152,220,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-info:before,[data-tooltip]:not(.is-loading).has-tooltip-info:before,[data-tooltip]:not([disabled]).has-tooltip-info:before{background-color:rgba(50,152,220,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-success:after,[data-tooltip]:not(.is-loading).has-tooltip-success:after,[data-tooltip]:not([disabled]).has-tooltip-success:after{border-color:rgba(72,199,116,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-success.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-success.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-success.has-tooltip-bottom:after{border-color:transparent transparent rgba(72,199,116,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-success.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-success.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-success.has-tooltip-left:after{border-color:transparent transparent transparent rgba(72,199,116,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-success.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-success.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-success.has-tooltip-right:after{border-color:transparent rgba(72,199,116,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-success:before,[data-tooltip]:not(.is-loading).has-tooltip-success:before,[data-tooltip]:not([disabled]).has-tooltip-success:before{background-color:rgba(72,199,116,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-warning:after,[data-tooltip]:not(.is-loading).has-tooltip-warning:after,[data-tooltip]:not([disabled]).has-tooltip-warning:after{border-color:rgba(255,221,87,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-warning.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-warning.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-warning.has-tooltip-bottom:after{border-color:transparent transparent rgba(255,221,87,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-warning.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-warning.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-warning.has-tooltip-left:after{border-color:transparent transparent transparent rgba(255,221,87,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-warning.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-warning.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-warning.has-tooltip-right:after{border-color:transparent rgba(255,221,87,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-warning:before,[data-tooltip]:not(.is-loading).has-tooltip-warning:before,[data-tooltip]:not([disabled]).has-tooltip-warning:before{background-color:rgba(255,221,87,.9);color:rgba(0,0,0,.7)}[data-tooltip]:not(.is-disabled).has-tooltip-danger:after,[data-tooltip]:not(.is-loading).has-tooltip-danger:after,[data-tooltip]:not([disabled]).has-tooltip-danger:after{border-color:rgba(241,70,104,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-danger.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-danger.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-danger.has-tooltip-bottom:after{border-color:transparent transparent rgba(241,70,104,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-danger.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-danger.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-danger.has-tooltip-left:after{border-color:transparent transparent transparent rgba(241,70,104,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-danger.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-danger.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-danger.has-tooltip-right:after{border-color:transparent rgba(241,70,104,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-danger:before,[data-tooltip]:not(.is-loading).has-tooltip-danger:before,[data-tooltip]:not([disabled]).has-tooltip-danger:before{background-color:rgba(241,70,104,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-active:after,[data-tooltip]:not(.is-disabled).has-tooltip-active:before,[data-tooltip]:not(.is-disabled):hover:after,[data-tooltip]:not(.is-disabled):hover:before,[data-tooltip]:not(.is-loading).has-tooltip-active:after,[data-tooltip]:not(.is-loading).has-tooltip-active:before,[data-tooltip]:not(.is-loading):hover:after,[data-tooltip]:not(.is-loading):hover:before,[data-tooltip]:not([disabled]).has-tooltip-active:after,[data-tooltip]:not([disabled]).has-tooltip-active:before,[data-tooltip]:not([disabled]):hover:after,[data-tooltip]:not([disabled]):hover:before{opacity:1;visibility:visible}[data-tooltip]:not(.is-disabled).has-tooltip-fade:after,[data-tooltip]:not(.is-disabled).has-tooltip-fade:before,[data-tooltip]:not(.is-loading).has-tooltip-fade:after,[data-tooltip]:not(.is-loading).has-tooltip-fade:before,[data-tooltip]:not([disabled]).has-tooltip-fade:after,[data-tooltip]:not([disabled]).has-tooltip-fade:before{transition:opacity .3s linear,visibility .3s linear}@media screen and (max-width:768px){[data-tooltip]:not(.is-disabled).has-tooltip-top-mobile.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-mobile.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-mobile.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-mobile:before,[data-tooltip]:not(.is-loading).has-tooltip-top-mobile:before,[data-tooltip]:not([disabled]).has-tooltip-top-mobile:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media print,screen and (min-width:769px){[data-tooltip]:not(.is-disabled).has-tooltip-top-tablet.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-tablet.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-tablet.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-tablet:before,[data-tooltip]:not(.is-loading).has-tooltip-top-tablet:before,[data-tooltip]:not([disabled]).has-tooltip-top-tablet:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (min-width:769px) and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-top-tablet-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-tablet-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-tablet-only.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-tablet-only:before,[data-tooltip]:not(.is-loading).has-tooltip-top-tablet-only:before,[data-tooltip]:not([disabled]).has-tooltip-top-tablet-only:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-top-touch.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-touch.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-touch.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-touch:before,[data-tooltip]:not(.is-loading).has-tooltip-top-touch:before,[data-tooltip]:not([disabled]).has-tooltip-top-touch:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (min-width:1024px){[data-tooltip]:not(.is-disabled).has-tooltip-top-desktop.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-desktop.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-desktop.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-desktop:before,[data-tooltip]:not(.is-loading).has-tooltip-top-desktop:before,[data-tooltip]:not([disabled]).has-tooltip-top-desktop:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (min-width:1024px) and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-top-desktop-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-desktop-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-desktop-only.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-desktop-only:before,[data-tooltip]:not(.is-loading).has-tooltip-top-desktop-only:before,[data-tooltip]:not([disabled]).has-tooltip-top-desktop-only:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-top-until-widescreen.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-until-widescreen.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-until-widescreen.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-until-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-top-until-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-top-until-widescreen:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (min-width:1216px){[data-tooltip]:not(.is-disabled).has-tooltip-top-widescreen.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-widescreen.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-widescreen.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-top-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-top-widescreen:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (min-width:1216px) and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-top-widescreen-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-widescreen-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-widescreen-only.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-widescreen-only:before,[data-tooltip]:not(.is-loading).has-tooltip-top-widescreen-only:before,[data-tooltip]:not([disabled]).has-tooltip-top-widescreen-only:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-top-until-fullhd.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-until-fullhd.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-until-fullhd.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-until-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-top-until-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-top-until-fullhd:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (min-width:1408px){[data-tooltip]:not(.is-disabled).has-tooltip-top-fullhd.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-fullhd.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-fullhd.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-top-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-top-fullhd:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (max-width:768px){[data-tooltip]:not(.is-disabled).has-tooltip-right-mobile.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-mobile.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-mobile.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-mobile:before,[data-tooltip]:not(.is-loading).has-tooltip-right-mobile:before,[data-tooltip]:not([disabled]).has-tooltip-right-mobile:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media print,screen and (min-width:769px){[data-tooltip]:not(.is-disabled).has-tooltip-right-tablet.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-tablet.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-tablet.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-tablet:before,[data-tooltip]:not(.is-loading).has-tooltip-right-tablet:before,[data-tooltip]:not([disabled]).has-tooltip-right-tablet:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (min-width:769px) and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-right-tablet-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-tablet-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-tablet-only.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-tablet-only:before,[data-tooltip]:not(.is-loading).has-tooltip-right-tablet-only:before,[data-tooltip]:not([disabled]).has-tooltip-right-tablet-only:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-right-touch.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-touch.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-touch.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-touch:before,[data-tooltip]:not(.is-loading).has-tooltip-right-touch:before,[data-tooltip]:not([disabled]).has-tooltip-right-touch:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (min-width:1024px){[data-tooltip]:not(.is-disabled).has-tooltip-right-desktop.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-desktop.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-desktop.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-desktop:before,[data-tooltip]:not(.is-loading).has-tooltip-right-desktop:before,[data-tooltip]:not([disabled]).has-tooltip-right-desktop:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (min-width:1024px) and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-right-desktop-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-desktop-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-desktop-only.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-desktop-only:before,[data-tooltip]:not(.is-loading).has-tooltip-right-desktop-only:before,[data-tooltip]:not([disabled]).has-tooltip-right-desktop-only:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-right-until-widescreen.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-until-widescreen.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-until-widescreen.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-until-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-right-until-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-right-until-widescreen:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (min-width:1216px){[data-tooltip]:not(.is-disabled).has-tooltip-right-widescreen.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-widescreen.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-widescreen.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-right-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-right-widescreen:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (min-width:1216px) and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-right-widescreen-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-widescreen-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-widescreen-only.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-widescreen-only:before,[data-tooltip]:not(.is-loading).has-tooltip-right-widescreen-only:before,[data-tooltip]:not([disabled]).has-tooltip-right-widescreen-only:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-right-until-fullhd.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-until-fullhd.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-until-fullhd.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-until-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-right-until-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-right-until-fullhd:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (min-width:1408px){[data-tooltip]:not(.is-disabled).has-tooltip-right-fullhd.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-fullhd.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-fullhd.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-right-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-right-fullhd:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (max-width:768px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-mobile.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-mobile.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-mobile.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-mobile:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-mobile:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-mobile:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media print,screen and (min-width:769px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-tablet.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-tablet.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-tablet.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-tablet:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-tablet:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-tablet:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (min-width:769px) and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-tablet-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-tablet-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-tablet-only.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-tablet-only:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-tablet-only:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-tablet-only:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-touch.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-touch.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-touch.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-touch:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-touch:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-touch:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (min-width:1024px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-desktop.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-desktop.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-desktop.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-desktop:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-desktop:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-desktop:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (min-width:1024px) and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-desktop-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-desktop-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-desktop-only.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-desktop-only:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-desktop-only:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-desktop-only:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-until-widescreen.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-until-widescreen.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-until-widescreen.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-until-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-until-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-until-widescreen:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (min-width:1216px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-widescreen.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-widescreen.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-widescreen.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-widescreen:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (min-width:1216px) and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-widescreen-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-widescreen-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-widescreen-only.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-widescreen-only:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-widescreen-only:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-widescreen-only:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-until-fullhd.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-until-fullhd.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-until-fullhd.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-until-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-until-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-until-fullhd:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (min-width:1408px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-fullhd.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-fullhd.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-fullhd.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-fullhd:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (max-width:768px){[data-tooltip]:not(.is-disabled).has-tooltip-left-mobile.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-mobile.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-mobile.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-mobile:before,[data-tooltip]:not(.is-loading).has-tooltip-left-mobile:before,[data-tooltip]:not([disabled]).has-tooltip-left-mobile:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media print,screen and (min-width:769px){[data-tooltip]:not(.is-disabled).has-tooltip-left-tablet.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-tablet.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-tablet.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-tablet:before,[data-tooltip]:not(.is-loading).has-tooltip-left-tablet:before,[data-tooltip]:not([disabled]).has-tooltip-left-tablet:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (min-width:769px) and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-left-tablet-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-tablet-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-tablet-only.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-tablet-only:before,[data-tooltip]:not(.is-loading).has-tooltip-left-tablet-only:before,[data-tooltip]:not([disabled]).has-tooltip-left-tablet-only:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-left-touch.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-touch.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-touch.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-touch:before,[data-tooltip]:not(.is-loading).has-tooltip-left-touch:before,[data-tooltip]:not([disabled]).has-tooltip-left-touch:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (min-width:1024px){[data-tooltip]:not(.is-disabled).has-tooltip-left-desktop.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-desktop.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-desktop.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-desktop:before,[data-tooltip]:not(.is-loading).has-tooltip-left-desktop:before,[data-tooltip]:not([disabled]).has-tooltip-left-desktop:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (min-width:1024px) and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-left-desktop-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-desktop-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-desktop-only.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-desktop-only:before,[data-tooltip]:not(.is-loading).has-tooltip-left-desktop-only:before,[data-tooltip]:not([disabled]).has-tooltip-left-desktop-only:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-left-until-widescreen.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-until-widescreen.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-until-widescreen.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-until-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-left-until-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-left-until-widescreen:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (min-width:1216px){[data-tooltip]:not(.is-disabled).has-tooltip-left-widescreen.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-widescreen.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-widescreen.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-left-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-left-widescreen:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (min-width:1216px) and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-left-widescreen-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-widescreen-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-widescreen-only.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-widescreen-only:before,[data-tooltip]:not(.is-loading).has-tooltip-left-widescreen-only:before,[data-tooltip]:not([disabled]).has-tooltip-left-widescreen-only:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-left-until-fullhd.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-until-fullhd.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-until-fullhd.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-until-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-left-until-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-left-until-fullhd:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (min-width:1408px){[data-tooltip]:not(.is-disabled).has-tooltip-left-fullhd.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-fullhd.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-fullhd.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-left-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-left-fullhd:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (max-width:768px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-mobile:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-mobile:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-mobile:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-mobile:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-mobile:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-mobile:before{opacity:0!important;display:none!important}}@media print,screen and (min-width:769px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-tablet:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-tablet:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-tablet:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-tablet:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-tablet:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-tablet:before{opacity:0!important;display:none!important}}@media screen and (min-width:769px) and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-tablet-only:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-tablet-only:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-tablet-only:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-tablet-only:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-tablet-only:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-tablet-only:before{opacity:0!important;display:none!important}}@media screen and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-touch:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-touch:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-touch:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-touch:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-touch:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-touch:before{opacity:0!important;display:none!important}}@media screen and (min-width:1024px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-desktop:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-desktop:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-desktop:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-desktop:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-desktop:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-desktop:before{opacity:0!important;display:none!important}}@media screen and (min-width:1024px) and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-desktop-only:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-desktop-only:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-desktop-only:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-desktop-only:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-desktop-only:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-desktop-only:before{opacity:0!important;display:none!important}}@media screen and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-until-widescreen:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-until-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-until-widescreen:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-until-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-until-widescreen:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-until-widescreen:before{opacity:0!important;display:none!important}}@media screen and (min-width:1216px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-widescreen:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-widescreen:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-widescreen:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-widescreen:before{opacity:0!important;display:none!important}}@media screen and (min-width:1216px) and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-widescreen-only:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-widescreen-only:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-widescreen-only:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-widescreen-only:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-widescreen-only:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-widescreen-only:before{opacity:0!important;display:none!important}}@media screen and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-until-fullhd:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-until-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-until-fullhd:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-until-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-until-fullhd:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-until-fullhd:before{opacity:0!important;display:none!important}}@media screen and (min-width:1408px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-fullhd:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-fullhd:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-fullhd:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-fullhd:before{opacity:0!important;display:none!important}}@media screen and (max-width:768px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-mobile:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-mobile:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-mobile:before{text-align:left}}@media print,screen and (min-width:769px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-tablet:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-tablet:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-tablet:before{text-align:left}}@media screen and (min-width:769px) and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-tablet-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-tablet-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-tablet-only:before{text-align:left}}@media screen and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-touch:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-touch:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-touch:before{text-align:left}}@media screen and (min-width:1024px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-desktop:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-desktop:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-desktop:before{text-align:left}}@media screen and (min-width:1024px) and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-desktop-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-desktop-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-desktop-only:before{text-align:left}}@media screen and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-until-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-until-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-until-widescreen:before{text-align:left}}@media screen and (min-width:1216px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-widescreen:before{text-align:left}}@media screen and (min-width:1216px) and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-widescreen-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-widescreen-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-widescreen-only:before{text-align:left}}@media screen and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-until-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-until-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-until-fullhd:before{text-align:left}}@media screen and (min-width:1408px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-fullhd:before{text-align:left}}@media screen and (max-width:768px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-mobile:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-mobile:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-mobile:before{text-align:center}}@media print,screen and (min-width:769px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-tablet:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-tablet:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-tablet:before{text-align:center}}@media screen and (min-width:769px) and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-tablet-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-tablet-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-tablet-only:before{text-align:center}}@media screen and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-touch:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-touch:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-touch:before{text-align:center}}@media screen and (min-width:1024px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-desktop:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-desktop:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-desktop:before{text-align:center}}@media screen and (min-width:1024px) and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-desktop-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-desktop-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-desktop-only:before{text-align:center}}@media screen and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-until-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-until-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-until-widescreen:before{text-align:center}}@media screen and (min-width:1216px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-widescreen:before{text-align:center}}@media screen and (min-width:1216px) and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-widescreen-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-widescreen-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-widescreen-only:before{text-align:center}}@media screen and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-until-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-until-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-until-fullhd:before{text-align:center}}@media screen and (min-width:1408px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-fullhd:before{text-align:center}}@media screen and (max-width:768px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-mobile:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-mobile:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-mobile:before{text-align:right}}@media print,screen and (min-width:769px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-tablet:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-tablet:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-tablet:before{text-align:right}}@media screen and (min-width:769px) and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-tablet-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-tablet-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-tablet-only:before{text-align:right}}@media screen and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-touch:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-touch:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-touch:before{text-align:right}}@media screen and (min-width:1024px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-desktop:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-desktop:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-desktop:before{text-align:right}}@media screen and (min-width:1024px) and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-desktop-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-desktop-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-desktop-only:before{text-align:right}}@media screen and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-until-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-until-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-until-widescreen:before{text-align:right}}@media screen and (min-width:1216px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-widescreen:before{text-align:right}}@media screen and (min-width:1216px) and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-widescreen-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-widescreen-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-widescreen-only:before{text-align:right}}@media screen and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-until-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-until-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-until-fullhd:before{text-align:right}}@media screen and (min-width:1408px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-fullhd:before{text-align:right}}span[data-tooltip]{border-bottom:1px dashed #dbdbdb}span[data-tooltip].has-tooltip-white{border-bottom-color:#fff}span[data-tooltip].has-tooltip-black{border-bottom-color:#171717}span[data-tooltip].has-tooltip-light{border-bottom-color:#fff}span[data-tooltip].has-tooltip-dark{border-bottom-color:#424242}span[data-tooltip].has-tooltip-primary{border-bottom-color:#00ebc7}span[data-tooltip].has-tooltip-link{border-bottom-color:#4882e0}span[data-tooltip].has-tooltip-info{border-bottom-color:#48a3e0}span[data-tooltip].has-tooltip-success{border-bottom-color:#5bcd83}span[data-tooltip].has-tooltip-warning{border-bottom-color:#ffe270}span[data-tooltip].has-tooltip-danger{border-bottom-color:#f35e7c}.control span[data-tooltip]{border-bottom:none}
+|]
+
+bulmaPageloaderCSS :: String
+bulmaPageloaderCSS = [s|
+@-webkit-keyframes spinAround{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes spinAround{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.pageloader{bottom:0;left:0;position:absolute;right:0;top:0}.pageloader{position:fixed;padding-top:2em;background:#00d1b2;background:#00d1b2;z-index:999998;transition:transform .35s ease-out,-webkit-transform .35s ease-out;will-change:transform}.pageloader.is-white{background-color:#fff;background:#fff}.pageloader.is-white::after{border-color:#0a0a0a;-webkit-animation:loader-figure-white 1.15s infinite cubic-bezier(.215,.61,.355,1);animation:loader-figure-white 1.15s infinite cubic-bezier(.215,.61,.355,1)}.pageloader.is-white .title{color:#0a0a0a}.pageloader.is-black{background-color:#0a0a0a;background:#0a0a0a}.pageloader.is-black::after{border-color:#fff;-webkit-animation:loader-figure-black 1.15s infinite cubic-bezier(.215,.61,.355,1);animation:loader-figure-black 1.15s infinite cubic-bezier(.215,.61,.355,1)}.pageloader.is-black .title{color:#fff}.pageloader.is-light{background-color:#f5f5f5;background:#f5f5f5}.pageloader.is-light::after{border-color:#363636;-webkit-animation:loader-figure-light 1.15s infinite cubic-bezier(.215,.61,.355,1);animation:loader-figure-light 1.15s infinite cubic-bezier(.215,.61,.355,1)}.pageloader.is-light .title{color:#363636}.pageloader.is-dark{background-color:#363636;background:#363636}.pageloader.is-dark::after{border-color:#f5f5f5;-webkit-animation:loader-figure-dark 1.15s infinite cubic-bezier(.215,.61,.355,1);animation:loader-figure-dark 1.15s infinite cubic-bezier(.215,.61,.355,1)}.pageloader.is-dark .title{color:#f5f5f5}.pageloader.is-primary{background-color:#00d1b2;background:#00d1b2}.pageloader.is-primary::after{border-color:#fff;-webkit-animation:loader-figure-primary 1.15s infinite cubic-bezier(.215,.61,.355,1);animation:loader-figure-primary 1.15s infinite cubic-bezier(.215,.61,.355,1)}.pageloader.is-primary .title{color:#fff}.pageloader.is-link{background-color:#3273dc;background:#3273dc}.pageloader.is-link::after{border-color:#fff;-webkit-animation:loader-figure-link 1.15s infinite cubic-bezier(.215,.61,.355,1);animation:loader-figure-link 1.15s infinite cubic-bezier(.215,.61,.355,1)}.pageloader.is-link .title{color:#fff}.pageloader.is-info{background-color:#209cee;background:#209cee}.pageloader.is-info::after{border-color:#fff;-webkit-animation:loader-figure-info 1.15s infinite cubic-bezier(.215,.61,.355,1);animation:loader-figure-info 1.15s infinite cubic-bezier(.215,.61,.355,1)}.pageloader.is-info .title{color:#fff}.pageloader.is-success{background-color:#23d160;background:#23d160}.pageloader.is-success::after{border-color:#fff;-webkit-animation:loader-figure-success 1.15s infinite cubic-bezier(.215,.61,.355,1);animation:loader-figure-success 1.15s infinite cubic-bezier(.215,.61,.355,1)}.pageloader.is-success .title{color:#fff}.pageloader.is-warning{background-color:#ffdd57;background:#ffdd57}.pageloader.is-warning::after{border-color:rgba(0,0,0,.7);-webkit-animation:loader-figure-warning 1.15s infinite cubic-bezier(.215,.61,.355,1);animation:loader-figure-warning 1.15s infinite cubic-bezier(.215,.61,.355,1)}.pageloader.is-warning .title{color:rgba(0,0,0,.7)}.pageloader.is-danger{background-color:#ff3860;background:#ff3860}.pageloader.is-danger::after{border-color:#fff;-webkit-animation:loader-figure-danger 1.15s infinite cubic-bezier(.215,.61,.355,1);animation:loader-figure-danger 1.15s infinite cubic-bezier(.215,.61,.355,1)}.pageloader.is-danger .title{color:#fff}.pageloader:not(.is-bottom-to-top){-webkit-transform:translateY(-100%);transform:translateY(-100%)}.pageloader.is-bottom-to-top{-webkit-transform:translateY(100%);transform:translateY(100%)}.pageloader.is-left-to-right{-webkit-transform:translateX(-100%);transform:translateX(-100%)}.pageloader.is-right-to-left{-webkit-transform:translateX(100%);transform:translateX(100%)}.pageloader.is-active:not(.is-left-to-right),.pageloader.is-active:not(.is-right-to-left){-webkit-transform:translateY(0);transform:translateY(0)}.pageloader.is-active.is-left-to-right,.pageloader.is-active.is-right-to-left{-webkit-transform:translateX(0);transform:translateX(0)}.pageloader::after{position:absolute;top:50%;left:50%;display:block;border-radius:100%;content:'';z-index:9999;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);width:0;height:0;box-sizing:border-box;border:0 solid #fff;-webkit-animation:loader-figure 1.15s infinite cubic-bezier(.215,.61,.355,1);animation:loader-figure 1.15s infinite cubic-bezier(.215,.61,.355,1)}.pageloader .title{position:absolute;top:50%;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%);margin:2em 0 0 0;font-size:.875em;letter-spacing:.1em;line-height:1.5em;color:#fff;white-space:nowrap}@-webkit-keyframes loader-figure{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@keyframes loader-figure{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@-webkit-keyframes loader-figure-white{0%{height:0;width:0;background-color:#0a0a0a}29%{background-color:#0a0a0a}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@keyframes loader-figure-white{0%{height:0;width:0;background-color:#0a0a0a}29%{background-color:#0a0a0a}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@-webkit-keyframes loader-figure-black{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@keyframes loader-figure-black{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@-webkit-keyframes loader-figure-light{0%{height:0;width:0;background-color:#363636}29%{background-color:#363636}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@keyframes loader-figure-light{0%{height:0;width:0;background-color:#363636}29%{background-color:#363636}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@-webkit-keyframes loader-figure-dark{0%{height:0;width:0;background-color:#f5f5f5}29%{background-color:#f5f5f5}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@keyframes loader-figure-dark{0%{height:0;width:0;background-color:#f5f5f5}29%{background-color:#f5f5f5}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@-webkit-keyframes loader-figure-primary{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@keyframes loader-figure-primary{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@-webkit-keyframes loader-figure-link{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@keyframes loader-figure-link{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@-webkit-keyframes loader-figure-info{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@keyframes loader-figure-info{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@-webkit-keyframes loader-figure-success{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@keyframes loader-figure-success{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@-webkit-keyframes loader-figure-warning{0%{height:0;width:0;background-color:rgba(0,0,0,.7)}29%{background-color:rgba(0,0,0,.7)}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@keyframes loader-figure-warning{0%{height:0;width:0;background-color:rgba(0,0,0,.7)}29%{background-color:rgba(0,0,0,.7)}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@-webkit-keyframes loader-figure-danger{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@keyframes loader-figure-danger{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}
+|]
+
+bulmaSwitchCSS :: String
+bulmaSwitchCSS = [s|
+.switch[type=checkbox]{outline:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;display:inline-block;position:absolute;opacity:0}.switch[type=checkbox]:focus+label::after,.switch[type=checkbox]:focus+label::before,.switch[type=checkbox]:focus+label:after,.switch[type=checkbox]:focus+label:before{outline:1px dotted #b5b5b5}.switch[type=checkbox][disabled]{cursor:not-allowed}.switch[type=checkbox][disabled]+label{opacity:.5}.switch[type=checkbox][disabled]+label::before,.switch[type=checkbox][disabled]+label:before{opacity:.5}.switch[type=checkbox][disabled]+label::after,.switch[type=checkbox][disabled]+label:after{opacity:.5}.switch[type=checkbox][disabled]+label:hover{cursor:not-allowed}.switch[type=checkbox]+label{position:relative;display:inline-flex;align-items:center;justify-content:flex-start;font-size:1rem;height:2.5em;line-height:1.5;padding-left:3.5rem;padding-top:.2rem;cursor:pointer}.switch[type=checkbox]+label::before,.switch[type=checkbox]+label:before{position:absolute;display:block;top:calc(50% - 1.5rem * .5);left:0;width:3rem;height:1.5rem;border:.1rem solid transparent;border-radius:4px;background:#b5b5b5;content:""}.switch[type=checkbox]+label::after,.switch[type=checkbox]+label:after{display:block;position:absolute;top:calc(50% - 1rem * .5);left:.25rem;width:1rem;height:1rem;transform:translate3d(0,0,0);border-radius:4px;background:#fff;transition:all .25s ease-out;content:""}.switch[type=checkbox]+label .switch-active,.switch[type=checkbox]+label .switch-inactive{font-size:.9rem;z-index:1;margin-top:-4px}.switch[type=checkbox]+label.has-text-inside .switch-inactive{margin-left:-1.925rem}.switch[type=checkbox]+label.has-text-inside .switch-active{margin-left:-3.25rem}.switch[type=checkbox].is-rtl+label{padding-left:0;padding-right:3.5rem}.switch[type=checkbox].is-rtl+label::before,.switch[type=checkbox].is-rtl+label:before{left:auto;right:0}.switch[type=checkbox].is-rtl+label::after,.switch[type=checkbox].is-rtl+label:after{left:auto;right:1.625rem}.switch[type=checkbox]:checked+label::before,.switch[type=checkbox]:checked+label:before{background:#00d1b2}.switch[type=checkbox]:checked+label::after{left:1.625rem}.switch[type=checkbox]:checked.is-rtl+label::after,.switch[type=checkbox]:checked.is-rtl+label:after{left:auto;right:.25rem}.switch[type=checkbox].is-outlined+label::before,.switch[type=checkbox].is-outlined+label:before{background-color:transparent;border-color:#b5b5b5}.switch[type=checkbox].is-outlined+label::after,.switch[type=checkbox].is-outlined+label:after{background:#b5b5b5}.switch[type=checkbox].is-outlined:checked+label::before,.switch[type=checkbox].is-outlined:checked+label:before{background-color:transparent;border-color:#00d1b2}.switch[type=checkbox].is-outlined:checked+label::after,.switch[type=checkbox].is-outlined:checked+label:after{background:#00d1b2}.switch[type=checkbox].is-thin+label::before,.switch[type=checkbox].is-thin+label:before{top:.5454545456rem;height:.375rem}.switch[type=checkbox].is-thin+label::after,.switch[type=checkbox].is-thin+label:after{box-shadow:0 0 3px #7a7a7a}.switch[type=checkbox].is-rounded+label::before,.switch[type=checkbox].is-rounded+label:before{border-radius:24px}.switch[type=checkbox].is-rounded+label::after,.switch[type=checkbox].is-rounded+label:after{border-radius:50%}.switch[type=checkbox].is-small+label{position:relative;display:inline-flex;align-items:center;justify-content:flex-start;font-size:.75rem;height:2.5em;line-height:1.5;padding-left:2.75rem;padding-top:.2rem;cursor:pointer}.switch[type=checkbox].is-small+label::before,.switch[type=checkbox].is-small+label:before{position:absolute;display:block;top:calc(50% - 1.125rem * .5);left:0;width:2.25rem;height:1.125rem;border:.1rem solid transparent;border-radius:4px;background:#b5b5b5;content:""}.switch[type=checkbox].is-small+label::after,.switch[type=checkbox].is-small+label:after{display:block;position:absolute;top:calc(50% - .625rem * .5);left:.25rem;width:.625rem;height:.625rem;transform:translate3d(0,0,0);border-radius:4px;background:#fff;transition:all .25s ease-out;content:""}.switch[type=checkbox].is-small+label .switch-active,.switch[type=checkbox].is-small+label .switch-inactive{font-size:.65rem;z-index:1;margin-top:-4px}.switch[type=checkbox].is-small+label.has-text-inside .switch-inactive{margin-left:-1.55rem}.switch[type=checkbox].is-small+label.has-text-inside .switch-active{margin-left:-2.5rem}.switch[type=checkbox].is-small.is-rtl+label{padding-left:0;padding-right:2.75rem}.switch[type=checkbox].is-small.is-rtl+label::before,.switch[type=checkbox].is-small.is-rtl+label:before{left:auto;right:0}.switch[type=checkbox].is-small.is-rtl+label::after,.switch[type=checkbox].is-small.is-rtl+label:after{left:auto;right:1.25rem}.switch[type=checkbox].is-small:checked+label::before,.switch[type=checkbox].is-small:checked+label:before{background:#00d1b2}.switch[type=checkbox].is-small:checked+label::after{left:1.25rem}.switch[type=checkbox].is-small:checked.is-rtl+label::after,.switch[type=checkbox].is-small:checked.is-rtl+label:after{left:auto;right:.25rem}.switch[type=checkbox].is-small.is-outlined+label::before,.switch[type=checkbox].is-small.is-outlined+label:before{background-color:transparent;border-color:#b5b5b5}.switch[type=checkbox].is-small.is-outlined+label::after,.switch[type=checkbox].is-small.is-outlined+label:after{background:#b5b5b5}.switch[type=checkbox].is-small.is-outlined:checked+label::before,.switch[type=checkbox].is-small.is-outlined:checked+label:before{background-color:transparent;border-color:#00d1b2}.switch[type=checkbox].is-small.is-outlined:checked+label::after,.switch[type=checkbox].is-small.is-outlined:checked+label:after{background:#00d1b2}.switch[type=checkbox].is-small.is-thin+label::before,.switch[type=checkbox].is-small.is-thin+label:before{top:.4090909093rem;height:.28125rem}.switch[type=checkbox].is-small.is-thin+label::after,.switch[type=checkbox].is-small.is-thin+label:after{box-shadow:0 0 3px #7a7a7a}.switch[type=checkbox].is-small.is-rounded+label::before,.switch[type=checkbox].is-small.is-rounded+label:before{border-radius:24px}.switch[type=checkbox].is-small.is-rounded+label::after,.switch[type=checkbox].is-small.is-rounded+label:after{border-radius:50%}.switch[type=checkbox].is-medium+label{position:relative;display:inline-flex;align-items:center;justify-content:flex-start;font-size:1.25rem;height:2.5em;line-height:1.5;padding-left:4.25rem;padding-top:.2rem;cursor:pointer}.switch[type=checkbox].is-medium+label::before,.switch[type=checkbox].is-medium+label:before{position:absolute;display:block;top:calc(50% - 1.875rem * .5);left:0;width:3.75rem;height:1.875rem;border:.1rem solid transparent;border-radius:4px;background:#b5b5b5;content:""}.switch[type=checkbox].is-medium+label::after,.switch[type=checkbox].is-medium+label:after{display:block;position:absolute;top:calc(50% - 1.375rem * .5);left:.25rem;width:1.375rem;height:1.375rem;transform:translate3d(0,0,0);border-radius:4px;background:#fff;transition:all .25s ease-out;content:""}.switch[type=checkbox].is-medium+label .switch-active,.switch[type=checkbox].is-medium+label .switch-inactive{font-size:1.15rem;z-index:1;margin-top:-4px}.switch[type=checkbox].is-medium+label.has-text-inside .switch-inactive{margin-left:-2.3rem}.switch[type=checkbox].is-medium+label.has-text-inside .switch-active{margin-left:-4rem}.switch[type=checkbox].is-medium.is-rtl+label{padding-left:0;padding-right:4.25rem}.switch[type=checkbox].is-medium.is-rtl+label::before,.switch[type=checkbox].is-medium.is-rtl+label:before{left:auto;right:0}.switch[type=checkbox].is-medium.is-rtl+label::after,.switch[type=checkbox].is-medium.is-rtl+label:after{left:auto;right:2rem}.switch[type=checkbox].is-medium:checked+label::before,.switch[type=checkbox].is-medium:checked+label:before{background:#00d1b2}.switch[type=checkbox].is-medium:checked+label::after{left:2rem}.switch[type=checkbox].is-medium:checked.is-rtl+label::after,.switch[type=checkbox].is-medium:checked.is-rtl+label:after{left:auto;right:.25rem}.switch[type=checkbox].is-medium.is-outlined+label::before,.switch[type=checkbox].is-medium.is-outlined+label:before{background-color:transparent;border-color:#b5b5b5}.switch[type=checkbox].is-medium.is-outlined+label::after,.switch[type=checkbox].is-medium.is-outlined+label:after{background:#b5b5b5}.switch[type=checkbox].is-medium.is-outlined:checked+label::before,.switch[type=checkbox].is-medium.is-outlined:checked+label:before{background-color:transparent;border-color:#00d1b2}.switch[type=checkbox].is-medium.is-outlined:checked+label::after,.switch[type=checkbox].is-medium.is-outlined:checked+label:after{background:#00d1b2}.switch[type=checkbox].is-medium.is-thin+label::before,.switch[type=checkbox].is-medium.is-thin+label:before{top:.6818181819rem;height:.46875rem}.switch[type=checkbox].is-medium.is-thin+label::after,.switch[type=checkbox].is-medium.is-thin+label:after{box-shadow:0 0 3px #7a7a7a}.switch[type=checkbox].is-medium.is-rounded+label::before,.switch[type=checkbox].is-medium.is-rounded+label:before{border-radius:24px}.switch[type=checkbox].is-medium.is-rounded+label::after,.switch[type=checkbox].is-medium.is-rounded+label:after{border-radius:50%}.switch[type=checkbox].is-large+label{position:relative;display:inline-flex;align-items:center;justify-content:flex-start;font-size:1.5rem;height:2.5em;line-height:1.5;padding-left:5rem;padding-top:.2rem;cursor:pointer}.switch[type=checkbox].is-large+label::before,.switch[type=checkbox].is-large+label:before{position:absolute;display:block;top:calc(50% - 2.25rem * .5);left:0;width:4.5rem;height:2.25rem;border:.1rem solid transparent;border-radius:4px;background:#b5b5b5;content:""}.switch[type=checkbox].is-large+label::after,.switch[type=checkbox].is-large+label:after{display:block;position:absolute;top:calc(50% - 1.75rem * .5);left:.25rem;width:1.75rem;height:1.75rem;transform:translate3d(0,0,0);border-radius:4px;background:#fff;transition:all .25s ease-out;content:""}.switch[type=checkbox].is-large+label .switch-active,.switch[type=checkbox].is-large+label .switch-inactive{font-size:1.4rem;z-index:1;margin-top:-4px}.switch[type=checkbox].is-large+label.has-text-inside .switch-inactive{margin-left:-2.675rem}.switch[type=checkbox].is-large+label.has-text-inside .switch-active{margin-left:-4.75rem}.switch[type=checkbox].is-large.is-rtl+label{padding-left:0;padding-right:5rem}.switch[type=checkbox].is-large.is-rtl+label::before,.switch[type=checkbox].is-large.is-rtl+label:before{left:auto;right:0}.switch[type=checkbox].is-large.is-rtl+label::after,.switch[type=checkbox].is-large.is-rtl+label:after{left:auto;right:2.375rem}.switch[type=checkbox].is-large:checked+label::before,.switch[type=checkbox].is-large:checked+label:before{background:#00d1b2}.switch[type=checkbox].is-large:checked+label::after{left:2.375rem}.switch[type=checkbox].is-large:checked.is-rtl+label::after,.switch[type=checkbox].is-large:checked.is-rtl+label:after{left:auto;right:.25rem}.switch[type=checkbox].is-large.is-outlined+label::before,.switch[type=checkbox].is-large.is-outlined+label:before{background-color:transparent;border-color:#b5b5b5}.switch[type=checkbox].is-large.is-outlined+label::after,.switch[type=checkbox].is-large.is-outlined+label:after{background:#b5b5b5}.switch[type=checkbox].is-large.is-outlined:checked+label::before,.switch[type=checkbox].is-large.is-outlined:checked+label:before{background-color:transparent;border-color:#00d1b2}.switch[type=checkbox].is-large.is-outlined:checked+label::after,.switch[type=checkbox].is-large.is-outlined:checked+label:after{background:#00d1b2}.switch[type=checkbox].is-large.is-thin+label::before,.switch[type=checkbox].is-large.is-thin+label:before{top:.8181818183rem;height:.5625rem}.switch[type=checkbox].is-large.is-thin+label::after,.switch[type=checkbox].is-large.is-thin+label:after{box-shadow:0 0 3px #7a7a7a}.switch[type=checkbox].is-large.is-rounded+label::before,.switch[type=checkbox].is-large.is-rounded+label:before{border-radius:24px}.switch[type=checkbox].is-large.is-rounded+label::after,.switch[type=checkbox].is-large.is-rounded+label:after{border-radius:50%}.switch[type=checkbox].is-white+label .switch-active{display:none}.switch[type=checkbox].is-white+label .switch-inactive{display:inline-block}.switch[type=checkbox].is-white:checked+label::before,.switch[type=checkbox].is-white:checked+label:before{background:#fff}.switch[type=checkbox].is-white:checked+label .switch-active{display:inline-block}.switch[type=checkbox].is-white:checked+label .switch-inactive{display:none}.switch[type=checkbox].is-white.is-outlined:checked+label::before,.switch[type=checkbox].is-white.is-outlined:checked+label:before{background-color:transparent;border-color:#fff!important}.switch[type=checkbox].is-white.is-outlined:checked+label::after,.switch[type=checkbox].is-white.is-outlined:checked+label:after{background:#fff}.switch[type=checkbox].is-white.is-thin.is-outlined+label::after,.switch[type=checkbox].is-white.is-thin.is-outlined+label:after{box-shadow:none}.switch[type=checkbox].is-unchecked-white+label::before,.switch[type=checkbox].is-unchecked-white+label:before{background:#fff}.switch[type=checkbox].is-unchecked-white.is-outlined+label::before,.switch[type=checkbox].is-unchecked-white.is-outlined+label:before{background-color:transparent;border-color:#fff!important}.switch[type=checkbox].is-unchecked-white.is-outlined+label::after,.switch[type=checkbox].is-unchecked-white.is-outlined+label:after{background:#fff}.switch[type=checkbox].is-black+label .switch-active{display:none}.switch[type=checkbox].is-black+label .switch-inactive{display:inline-block}.switch[type=checkbox].is-black:checked+label::before,.switch[type=checkbox].is-black:checked+label:before{background:#0a0a0a}.switch[type=checkbox].is-black:checked+label .switch-active{display:inline-block}.switch[type=checkbox].is-black:checked+label .switch-inactive{display:none}.switch[type=checkbox].is-black.is-outlined:checked+label::before,.switch[type=checkbox].is-black.is-outlined:checked+label:before{background-color:transparent;border-color:#0a0a0a!important}.switch[type=checkbox].is-black.is-outlined:checked+label::after,.switch[type=checkbox].is-black.is-outlined:checked+label:after{background:#0a0a0a}.switch[type=checkbox].is-black.is-thin.is-outlined+label::after,.switch[type=checkbox].is-black.is-thin.is-outlined+label:after{box-shadow:none}.switch[type=checkbox].is-unchecked-black+label::before,.switch[type=checkbox].is-unchecked-black+label:before{background:#0a0a0a}.switch[type=checkbox].is-unchecked-black.is-outlined+label::before,.switch[type=checkbox].is-unchecked-black.is-outlined+label:before{background-color:transparent;border-color:#0a0a0a!important}.switch[type=checkbox].is-unchecked-black.is-outlined+label::after,.switch[type=checkbox].is-unchecked-black.is-outlined+label:after{background:#0a0a0a}.switch[type=checkbox].is-light+label .switch-active{display:none}.switch[type=checkbox].is-light+label .switch-inactive{display:inline-block}.switch[type=checkbox].is-light:checked+label::before,.switch[type=checkbox].is-light:checked+label:before{background:#f5f5f5}.switch[type=checkbox].is-light:checked+label .switch-active{display:inline-block}.switch[type=checkbox].is-light:checked+label .switch-inactive{display:none}.switch[type=checkbox].is-light.is-outlined:checked+label::before,.switch[type=checkbox].is-light.is-outlined:checked+label:before{background-color:transparent;border-color:#f5f5f5!important}.switch[type=checkbox].is-light.is-outlined:checked+label::after,.switch[type=checkbox].is-light.is-outlined:checked+label:after{background:#f5f5f5}.switch[type=checkbox].is-light.is-thin.is-outlined+label::after,.switch[type=checkbox].is-light.is-thin.is-outlined+label:after{box-shadow:none}.switch[type=checkbox].is-unchecked-light+label::before,.switch[type=checkbox].is-unchecked-light+label:before{background:#f5f5f5}.switch[type=checkbox].is-unchecked-light.is-outlined+label::before,.switch[type=checkbox].is-unchecked-light.is-outlined+label:before{background-color:transparent;border-color:#f5f5f5!important}.switch[type=checkbox].is-unchecked-light.is-outlined+label::after,.switch[type=checkbox].is-unchecked-light.is-outlined+label:after{background:#f5f5f5}.switch[type=checkbox].is-dark+label .switch-active{display:none}.switch[type=checkbox].is-dark+label .switch-inactive{display:inline-block}.switch[type=checkbox].is-dark:checked+label::before,.switch[type=checkbox].is-dark:checked+label:before{background:#363636}.switch[type=checkbox].is-dark:checked+label .switch-active{display:inline-block}.switch[type=checkbox].is-dark:checked+label .switch-inactive{display:none}.switch[type=checkbox].is-dark.is-outlined:checked+label::before,.switch[type=checkbox].is-dark.is-outlined:checked+label:before{background-color:transparent;border-color:#363636!important}.switch[type=checkbox].is-dark.is-outlined:checked+label::after,.switch[type=checkbox].is-dark.is-outlined:checked+label:after{background:#363636}.switch[type=checkbox].is-dark.is-thin.is-outlined+label::after,.switch[type=checkbox].is-dark.is-thin.is-outlined+label:after{box-shadow:none}.switch[type=checkbox].is-unchecked-dark+label::before,.switch[type=checkbox].is-unchecked-dark+label:before{background:#363636}.switch[type=checkbox].is-unchecked-dark.is-outlined+label::before,.switch[type=checkbox].is-unchecked-dark.is-outlined+label:before{background-color:transparent;border-color:#363636!important}.switch[type=checkbox].is-unchecked-dark.is-outlined+label::after,.switch[type=checkbox].is-unchecked-dark.is-outlined+label:after{background:#363636}.switch[type=checkbox].is-primary+label .switch-active{display:none}.switch[type=checkbox].is-primary+label .switch-inactive{display:inline-block}.switch[type=checkbox].is-primary:checked+label::before,.switch[type=checkbox].is-primary:checked+label:before{background:#00d1b2}.switch[type=checkbox].is-primary:checked+label .switch-active{display:inline-block}.switch[type=checkbox].is-primary:checked+label .switch-inactive{display:none}.switch[type=checkbox].is-primary.is-outlined:checked+label::before,.switch[type=checkbox].is-primary.is-outlined:checked+label:before{background-color:transparent;border-color:#00d1b2!important}.switch[type=checkbox].is-primary.is-outlined:checked+label::after,.switch[type=checkbox].is-primary.is-outlined:checked+label:after{background:#00d1b2}.switch[type=checkbox].is-primary.is-thin.is-outlined+label::after,.switch[type=checkbox].is-primary.is-thin.is-outlined+label:after{box-shadow:none}.switch[type=checkbox].is-unchecked-primary+label::before,.switch[type=checkbox].is-unchecked-primary+label:before{background:#00d1b2}.switch[type=checkbox].is-unchecked-primary.is-outlined+label::before,.switch[type=checkbox].is-unchecked-primary.is-outlined+label:before{background-color:transparent;border-color:#00d1b2!important}.switch[type=checkbox].is-unchecked-primary.is-outlined+label::after,.switch[type=checkbox].is-unchecked-primary.is-outlined+label:after{background:#00d1b2}.switch[type=checkbox].is-link+label .switch-active{display:none}.switch[type=checkbox].is-link+label .switch-inactive{display:inline-block}.switch[type=checkbox].is-link:checked+label::before,.switch[type=checkbox].is-link:checked+label:before{background:#485fc7}.switch[type=checkbox].is-link:checked+label .switch-active{display:inline-block}.switch[type=checkbox].is-link:checked+label .switch-inactive{display:none}.switch[type=checkbox].is-link.is-outlined:checked+label::before,.switch[type=checkbox].is-link.is-outlined:checked+label:before{background-color:transparent;border-color:#485fc7!important}.switch[type=checkbox].is-link.is-outlined:checked+label::after,.switch[type=checkbox].is-link.is-outlined:checked+label:after{background:#485fc7}.switch[type=checkbox].is-link.is-thin.is-outlined+label::after,.switch[type=checkbox].is-link.is-thin.is-outlined+label:after{box-shadow:none}.switch[type=checkbox].is-unchecked-link+label::before,.switch[type=checkbox].is-unchecked-link+label:before{background:#485fc7}.switch[type=checkbox].is-unchecked-link.is-outlined+label::before,.switch[type=checkbox].is-unchecked-link.is-outlined+label:before{background-color:transparent;border-color:#485fc7!important}.switch[type=checkbox].is-unchecked-link.is-outlined+label::after,.switch[type=checkbox].is-unchecked-link.is-outlined+label:after{background:#485fc7}.switch[type=checkbox].is-info+label .switch-active{display:none}.switch[type=checkbox].is-info+label .switch-inactive{display:inline-block}.switch[type=checkbox].is-info:checked+label::before,.switch[type=checkbox].is-info:checked+label:before{background:#3e8ed0}.switch[type=checkbox].is-info:checked+label .switch-active{display:inline-block}.switch[type=checkbox].is-info:checked+label .switch-inactive{display:none}.switch[type=checkbox].is-info.is-outlined:checked+label::before,.switch[type=checkbox].is-info.is-outlined:checked+label:before{background-color:transparent;border-color:#3e8ed0!important}.switch[type=checkbox].is-info.is-outlined:checked+label::after,.switch[type=checkbox].is-info.is-outlined:checked+label:after{background:#3e8ed0}.switch[type=checkbox].is-info.is-thin.is-outlined+label::after,.switch[type=checkbox].is-info.is-thin.is-outlined+label:after{box-shadow:none}.switch[type=checkbox].is-unchecked-info+label::before,.switch[type=checkbox].is-unchecked-info+label:before{background:#3e8ed0}.switch[type=checkbox].is-unchecked-info.is-outlined+label::before,.switch[type=checkbox].is-unchecked-info.is-outlined+label:before{background-color:transparent;border-color:#3e8ed0!important}.switch[type=checkbox].is-unchecked-info.is-outlined+label::after,.switch[type=checkbox].is-unchecked-info.is-outlined+label:after{background:#3e8ed0}.switch[type=checkbox].is-success+label .switch-active{display:none}.switch[type=checkbox].is-success+label .switch-inactive{display:inline-block}.switch[type=checkbox].is-success:checked+label::before,.switch[type=checkbox].is-success:checked+label:before{background:#48c78e}.switch[type=checkbox].is-success:checked+label .switch-active{display:inline-block}.switch[type=checkbox].is-success:checked+label .switch-inactive{display:none}.switch[type=checkbox].is-success.is-outlined:checked+label::before,.switch[type=checkbox].is-success.is-outlined:checked+label:before{background-color:transparent;border-color:#48c78e!important}.switch[type=checkbox].is-success.is-outlined:checked+label::after,.switch[type=checkbox].is-success.is-outlined:checked+label:after{background:#48c78e}.switch[type=checkbox].is-success.is-thin.is-outlined+label::after,.switch[type=checkbox].is-success.is-thin.is-outlined+label:after{box-shadow:none}.switch[type=checkbox].is-unchecked-success+label::before,.switch[type=checkbox].is-unchecked-success+label:before{background:#48c78e}.switch[type=checkbox].is-unchecked-success.is-outlined+label::before,.switch[type=checkbox].is-unchecked-success.is-outlined+label:before{background-color:transparent;border-color:#48c78e!important}.switch[type=checkbox].is-unchecked-success.is-outlined+label::after,.switch[type=checkbox].is-unchecked-success.is-outlined+label:after{background:#48c78e}.switch[type=checkbox].is-warning+label .switch-active{display:none}.switch[type=checkbox].is-warning+label .switch-inactive{display:inline-block}.switch[type=checkbox].is-warning:checked+label::before,.switch[type=checkbox].is-warning:checked+label:before{background:#ffe08a}.switch[type=checkbox].is-warning:checked+label .switch-active{display:inline-block}.switch[type=checkbox].is-warning:checked+label .switch-inactive{display:none}.switch[type=checkbox].is-warning.is-outlined:checked+label::before,.switch[type=checkbox].is-warning.is-outlined:checked+label:before{background-color:transparent;border-color:#ffe08a!important}.switch[type=checkbox].is-warning.is-outlined:checked+label::after,.switch[type=checkbox].is-warning.is-outlined:checked+label:after{background:#ffe08a}.switch[type=checkbox].is-warning.is-thin.is-outlined+label::after,.switch[type=checkbox].is-warning.is-thin.is-outlined+label:after{box-shadow:none}.switch[type=checkbox].is-unchecked-warning+label::before,.switch[type=checkbox].is-unchecked-warning+label:before{background:#ffe08a}.switch[type=checkbox].is-unchecked-warning.is-outlined+label::before,.switch[type=checkbox].is-unchecked-warning.is-outlined+label:before{background-color:transparent;border-color:#ffe08a!important}.switch[type=checkbox].is-unchecked-warning.is-outlined+label::after,.switch[type=checkbox].is-unchecked-warning.is-outlined+label:after{background:#ffe08a}.switch[type=checkbox].is-danger+label .switch-active{display:none}.switch[type=checkbox].is-danger+label .switch-inactive{display:inline-block}.switch[type=checkbox].is-danger:checked+label::before,.switch[type=checkbox].is-danger:checked+label:before{background:#f14668}.switch[type=checkbox].is-danger:checked+label .switch-active{display:inline-block}.switch[type=checkbox].is-danger:checked+label .switch-inactive{display:none}.switch[type=checkbox].is-danger.is-outlined:checked+label::before,.switch[type=checkbox].is-danger.is-outlined:checked+label:before{background-color:transparent;border-color:#f14668!important}.switch[type=checkbox].is-danger.is-outlined:checked+label::after,.switch[type=checkbox].is-danger.is-outlined:checked+label:after{background:#f14668}.switch[type=checkbox].is-danger.is-thin.is-outlined+label::after,.switch[type=checkbox].is-danger.is-thin.is-outlined+label:after{box-shadow:none}.switch[type=checkbox].is-unchecked-danger+label::before,.switch[type=checkbox].is-unchecked-danger+label:before{background:#f14668}.switch[type=checkbox].is-unchecked-danger.is-outlined+label::before,.switch[type=checkbox].is-unchecked-danger.is-outlined+label:before{background-color:transparent;border-color:#f14668!important}.switch[type=checkbox].is-unchecked-danger.is-outlined+label::after,.switch[type=checkbox].is-unchecked-danger.is-outlined+label:after{background:#f14668}.field-body .switch[type=checkbox]+label{margin-top:.375em}
+|]
+
+bulmaDividerCSS :: String
+bulmaDividerCSS = [s|
+/*! @creativebulma/bulma-divider v1.1.0 | (c) 2020 Gaetan | MIT License | https://github.com/CreativeBulma/bulma-divider */
+.divider{position:relative;display:flex;align-items:center;text-transform:uppercase;color:#7a7a7a;font-size:.75rem;font-weight:600;letter-spacing:.5px;margin:25px 0}.divider:after,.divider:before{content:"";display:block;flex:1;height:1px;background-color:#dbdbdb}.divider:not(.is-right):after{margin-left:10px}.divider:not(.is-left):before{margin-right:10px}.divider.is-left:before,.divider.is-right:after{display:none}.divider.is-vertical{flex-direction:column;margin:0 25px}.divider.is-vertical:after,.divider.is-vertical:before{height:auto;width:1px}.divider.is-vertical:after{margin-left:0;margin-top:10px}.divider.is-vertical:before{margin-right:0;margin-bottom:10px}.divider.is-white:after,.divider.is-white:before{background-color:#fff}.divider.is-black:after,.divider.is-black:before{background-color:#0a0a0a}.divider.is-light:after,.divider.is-light:before{background-color:#f5f5f5}.divider.is-dark:after,.divider.is-dark:before{background-color:#363636}.divider.is-primary:after,.divider.is-primary:before{background-color:#00d1b2}.divider.is-primary.is-light:after,.divider.is-primary.is-light:before{background-color:#ebfffc}.divider.is-link:after,.divider.is-link:before{background-color:#3273dc}.divider.is-link.is-light:after,.divider.is-link.is-light:before{background-color:#eef3fc}.divider.is-info:after,.divider.is-info:before{background-color:#3298dc}.divider.is-info.is-light:after,.divider.is-info.is-light:before{background-color:#eef6fc}.divider.is-success:after,.divider.is-success:before{background-color:#48c774}.divider.is-success.is-light:after,.divider.is-success.is-light:before{background-color:#effaf3}.divider.is-warning:after,.divider.is-warning:before{background-color:#ffdd57}.divider.is-warning.is-light:after,.divider.is-warning.is-light:before{background-color:#fffbeb}.divider.is-danger:after,.divider.is-danger:before{background-color:#f14668}.divider.is-danger.is-light:after,.divider.is-danger.is-light:before{background-color:#feecf0}
+|]
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/CSS/Own.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/CSS/Own.hs
new file mode 100644
index 00000000000..ba4e50b526c
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/CSS/Own.hs
@@ -0,0 +1,875 @@
+{-# LANGUAGE QuasiQuotes #-}
+
+module Cardano.Tracer.Handlers.RTView.UI.CSS.Own
+ ( ownCSS
+ , chartGridDark
+ , chartGridLight
+ , chartTextDark
+ , chartTextLight
+ ) where
+
+import Data.String.QQ
+
+-- | To avoid run-time dependency from the static content, embed own CSS in the page's header.
+ownCSS :: String
+ownCSS = [s|
+html {
+ height: 100%;
+}
+
+body {
+ height: 100%;
+}
+
+.wrapper {
+ min-height: 100%;
+ width: 100%;
+ position: relative;
+ padding-bottom: 62px;
+ box-sizing: border-box;
+}
+
+.footer {
+ height: 62px;
+ bottom: 0;
+ position: absolute;
+ width: 100%;
+}
+
+code {
+ color: #1d359f;
+ padding: 0.11em 0.2em 0.11em;
+ border-radius: 3px;
+}
+
+pre {
+ color: #1d359f;
+ font-size: .875em;
+ overflow-x: auto;
+ padding: 8px 8px 8px 8px;
+ margin-top: 12px;
+ margin-bottom: 12px;
+ white-space: pre;
+ word-wrap: normal;
+ border-radius: 3px;
+}
+
+.message.is-link .message-body {
+ border-color: #485fc7;
+ color: #1834ae;
+}
+
+span[data-tooltip] {
+ border-bottom: none !important;
+}
+
+.pageloader {
+ opacity: 0.98 !important;
+}
+
+.field.is-grouped {
+ display: inline-flex !important;
+}
+
+.divider {
+ font-size: 16px;
+ letter-spacing: .5px;
+ margin: 30px 0;
+}
+
+.rt-view-show-hide-chart-group {
+ margin-top: 5px;
+}
+
+.rt-view-no-nodes-icon svg {
+ width: 70px;
+ margin-top: 60px;
+ margin-bottom: 40px;
+}
+
+.rt-view-no-nodes-info {
+ max-width: 800px !important;
+ margin-top: 50px;
+ font-size: 97%;
+}
+
+.rt-view-chart-area {
+ width: 100% !important;
+}
+
+.rt-view-main-table-description {
+ min-width: 380px;
+}
+
+.rt-view-node-chart-label svg {
+ width: 42px;
+ height: 15px;
+ margin-right: 3px;
+}
+
+.rt-view-peer-modal {
+ width: 45%;
+}
+
+.rt-view-errors-modal {
+ width: 65%;
+ min-height: 65%;
+}
+
+.rt-view-ekg-metrics-modal {
+ width: 50%;
+}
+
+@media only screen and (max-width: 1216px) {
+ .rt-view-peer-modal {
+ width: 60%;
+ }
+
+ .rt-view-errors-modal {
+ width: 70%;
+ }
+
+ .rt-view-ekg-metrics-modal {
+ width: 60%;
+ }
+}
+
+@media only screen and (max-width: 1024px) {
+ .rt-view-peer-modal {
+ width: 70%;
+ }
+
+ .rt-view-errors-modal {
+ width: 75%;
+ }
+
+ .rt-view-ekg-metrics-modal {
+ width: 70%;
+ }
+}
+
+@media only screen and (max-width: 769px) {
+ .rt-view-peer-modal {
+ width: 80%;
+ }
+
+ .rt-view-errors-modal {
+ width: 85%;
+ }
+
+ .rt-view-ekg-metrics-modal {
+ width: 80%;
+ }
+}
+
+.rt-view-logs-input {
+ max-width: 150px;
+}
+
+.rt-view-error-msg-input {
+ max-width: 380px;
+}
+
+.rt-view-errors-timestamp {
+ width: 30%;
+}
+
+.rt-view-errors-severity {
+ width: 16%;
+}
+
+.rt-view-copy-icon {
+ margin-top: 6px;
+}
+
+.rt-view-search-errors-icon {
+ margin-top: 6px;
+}
+
+/* Dark Theme */
+
+.dark {
+ font-family: sans-serif;
+ font-size: 22px;
+ background-color: #131325;
+ min-height: 100%;
+}
+
+.dark .wrapper {
+ background-color: #131325;
+}
+
+.dark .rt-view-href {
+ color: #607bf7;
+}
+
+.dark .rt-view-href:hover {
+ color: #889cf5 !important;
+ border-bottom: 1px solid #889cf5;
+}
+
+.dark .navbar-link, a.navbar-item {
+ font-size: 18px;
+ cursor: pointer;
+}
+
+.dark .rt-view-href-icon svg {
+ width: 12px;
+ margin-left: 5px;
+ margin-bottom: 4px;
+ color: #607bf7;
+}
+
+.dark .rt-view-top-bar {
+ background-color: #282841;
+ color: whitesmoke;
+ padding-top: 8px;
+ padding-bottom: 2px;
+ border-bottom: 1px solid #555;
+}
+
+.dark .rt-view-cardano-logo svg {
+ width: 48px;
+ color: whitesmoke;
+ margin-left: 5px;
+}
+
+.dark .rt-view-name {
+ color: whitesmoke;
+ margin-left: 17px;
+ margin-right: 6px;
+ margin-bottom: 6px;
+}
+
+.dark .rt-view-info-icon svg {
+ width: 25px;
+ padding-top: 2px;
+ color: whitesmoke;
+ cursor: pointer;
+}
+
+.dark .rt-view-theme-icon svg {
+ width: 23px;
+ padding-top: 2px;
+ color: whitesmoke;
+ cursor: pointer;
+}
+
+.dark .rt-view-copy-icon svg {
+ width: 20px;
+ color: whitesmoke;
+ cursor: pointer;
+}
+
+.dark .rt-view-sort-icon svg {
+ width: 11px;
+ margin-left: 9px;
+ color: whitesmoke;
+ cursor: pointer;
+}
+
+.dark .rt-view-delete-icon svg {
+ width: 20px;
+ color: red;
+ cursor: pointer;
+}
+
+.dark .rt-view-delete-errors-icon svg {
+ width: 22px;
+ color: red;
+ cursor: pointer;
+}
+
+.dark .rt-view-search-errors-icon svg {
+ width: 18px;
+ color: whitesmoke;
+ cursor: pointer;
+}
+
+.dark .rt-view-logs-icon svg {
+ width: 23px;
+ padding-top: 2px;
+ color: whitesmoke;
+}
+
+.dark .rt-view-overview-icon svg {
+ width: 18px;
+ margin-right: 12px;
+ color: #0cc9cb;
+}
+
+.dark .rt-view-no-nodes-icon svg {
+ color: #677deb;
+}
+
+.dark .rt-view-notify-menu-icon svg {
+ width: 15px;
+ padding-top: 6px;
+ margin-right: 9px;
+ color: whitesmoke;
+}
+
+.dark .rt-view-no-nodes-message {
+ font-size: 23px;
+ color: whitesmoke;
+}
+
+.dark .rt-view-charts-container {
+ padding-bottom: 0px;
+}
+
+.dark .rt-view-chart-container {
+ background-color: #2c2b3b;
+ padding-top: 10px;
+ padding-bottom: 20px;
+ padding-left: 20px;
+ padding-right: 20px;
+ margin-bottom: 24px;
+ border: 1px solid #444;
+ border-radius: 6px;
+}
+
+.dark .rt-view-chart-name {
+ color: whitesmoke;
+}
+
+.dark .rt-view-about-title {
+ color: whitesmoke;
+}
+
+.dark .rt-view-about-head {
+ color: whitesmoke;
+ background-color: #282841;
+ border-bottom: 1px solid #555;
+}
+
+.dark .rt-view-about-body {
+ color: whitesmoke;
+ background-color: #131325;
+ border-bottom-left-radius: 6px;
+ border-bottom-right-radius: 6px;
+}
+
+.dark .rt-view-peer-title {
+ color: whitesmoke;
+}
+
+.dark .rt-view-peer-head {
+ color: whitesmoke;
+ background-color: #282841;
+ border-bottom: 1px solid #555;
+}
+
+.dark .rt-view-peer-body {
+ color: whitesmoke;
+ background-color: #131325;
+ border-bottom-left-radius: 6px;
+ border-bottom-right-radius: 6px;
+}
+
+.dark .rt-view-errors-title {
+ color: whitesmoke;
+}
+
+.dark .rt-view-errors-head {
+ color: whitesmoke;
+ background-color: #282841;
+ border-bottom: 1px solid #555;
+}
+
+.dark .rt-view-errors-body {
+ color: whitesmoke;
+ background-color: #131325;
+ border-bottom-left-radius: 6px;
+ border-bottom-right-radius: 6px;
+}
+
+.dark .rt-view-errors-foot {
+ color: whitesmoke;
+ background-color: #282841;
+ border-top: 1px solid #555;
+}
+
+.dark .rt-view-notifications-title {
+ color: whitesmoke;
+}
+
+.dark .rt-view-notifications-head {
+ color: whitesmoke;
+ background-color: #282841;
+ border-bottom: 1px solid #555;
+}
+
+.dark .rt-view-notifications-body {
+ color: whitesmoke;
+ background-color: #131325;
+ border-bottom-left-radius: 6px;
+ border-bottom-right-radius: 6px;
+}
+
+.dark .rt-view-ekg-metrics-title {
+ color: whitesmoke;
+}
+
+.dark .rt-view-ekg-metrics-head {
+ color: whitesmoke;
+ background-color: #282841;
+ border-bottom: 1px solid #555;
+}
+
+.dark .rt-view-ekg-metrics-body {
+ color: whitesmoke;
+ background-color: #131325;
+ border-bottom-left-radius: 6px;
+ border-bottom-right-radius: 6px;
+}
+
+.dark .rt-view-main-table {
+ background-color: #131325;
+ color: whitesmoke;
+}
+
+.dark .rt-view-main-table td {
+ padding-top: 17px;
+ padding-bottom: 17px;
+ border-bottom: 0px solid #444;
+}
+
+.dark .rt-view-main-table th {
+ color: whitesmoke;
+ border-bottom: 2px solid #888;
+ vertical-align: middle;
+}
+
+.dark .rt-view-peer-table {
+ background-color: #131325;
+ color: whitesmoke;
+}
+
+.dark .rt-view-peer-table td {
+ padding-top: 10px;
+ padding-bottom: 10px;
+ border-bottom: 0px solid #444;
+}
+
+.dark .rt-view-peer-table th {
+ color: whitesmoke;
+ border-bottom: 2px solid #888;
+ vertical-align: middle;
+}
+
+.dark .rt-view-errors-table {
+ background-color: #131325;
+ color: whitesmoke;
+}
+
+.dark .rt-view-errors-table td {
+ padding-top: 10px;
+ padding-bottom: 10px;
+ border-bottom: 0px solid #444;
+}
+
+.dark .rt-view-errors-table th {
+ color: whitesmoke;
+ border-bottom: 2px solid #888;
+ vertical-align: middle;
+}
+
+.dark .rt-view-chart-group-title {
+ color: whitesmoke;
+ font-weight: bold;
+ margin-left: 13px;
+ font-size: 115%;
+}
+
+.dark .rt-view-show-hide-chart-group svg {
+ width: 21px;
+ color: #0cc9cb;
+}
+
+.dark .rt-view-chart-icon svg {
+ width: 19px;
+ margin-right: 11px;
+ color: #0cc9cb;
+}
+
+.dark .rt-view-footer {
+ background-color: #282841;
+ color: #999;
+ padding-top: 16px;
+ padding-bottom: 12px;
+ border-top: 1px solid #555;
+ font-size: 80%;
+}
+
+.dark .rt-view-footer-github svg {
+ width: 23px;
+ margin-top: 1px;
+ color: #0cc9cb;
+}
+
+.dark .rt-view-percent-done {
+ color: #07e949;
+}
+
+.dark .rt-view-what-icon svg {
+ width: 18px;
+ margin-left: 12px;
+ color: #999;
+}
+
+.dark .rt-view-epoch-end svg {
+ width: 16px;
+ margin-left: 20px;
+ margin-right: 5px;
+ color: #0cc9cb;
+}
+
+/**** Light Theme ****/
+
+.light {
+ font-family: sans-serif;
+ font-size: 22px;
+ background-color: #f5f5f5;
+ min-height: 100%;
+}
+
+.light .wrapper {
+ background-color: #f5f5f5;
+}
+
+.light .rt-view-href {
+ color: #264af0;
+}
+
+.light .rt-view-href:hover {
+ color: #889cf5 !important;
+ border-bottom: 1px solid #889cf5;
+}
+
+.light .rt-view-href-icon svg {
+ width: 12px;
+ margin-left: 5px;
+ margin-bottom: 4px;
+ color: #264af0;
+}
+
+.light .navbar-link, a.navbar-item {
+ font-size: 18px;
+ cursor: pointer;
+}
+
+.light .rt-view-top-bar {
+ background-color: #efefef;
+ color: #131325;
+ padding-top: 8px;
+ padding-bottom: 2px;
+ border-bottom: 1px solid #dbdbdb;
+}
+
+.light .rt-view-cardano-logo svg {
+ width: 48px;
+ color: #0033ad;
+ margin-left: 5px;
+}
+
+.light .rt-view-name {
+ color: #0033ad;
+ margin-left: 17px;
+ margin-right: 6px;
+ margin-bottom: 6px;
+}
+
+.light .rt-view-info-icon svg {
+ width: 25px;
+ padding-top: 2px;
+ color: #0033ad;
+ cursor: pointer;
+}
+
+.light .rt-view-theme-icon svg {
+ width: 23px;
+ padding-top: 2px;
+ color: #0033ad;
+ cursor: pointer;
+}
+
+.light .rt-view-copy-icon svg {
+ width: 20px;
+ color: #444;
+ cursor: pointer;
+}
+
+.light .rt-view-sort-icon svg {
+ width: 11px;
+ margin-left: 9px;
+ color: #444;
+ cursor: pointer;
+}
+
+.light .rt-view-delete-icon svg {
+ width: 20px;
+ color: red;
+ cursor: pointer;
+}
+
+.light .rt-view-delete-errors-icon svg {
+ width: 22px;
+ color: red;
+ cursor: pointer;
+}
+
+.light .rt-view-search-errors-icon svg {
+ width: 18px;
+ color: #444;
+ cursor: pointer;
+}
+
+.light .rt-view-logs-icon svg {
+ width: 23px;
+ padding-top: 2px;
+ color: #0033ad;
+}
+
+.light .rt-view-overview-icon svg {
+ width: 18px;
+ margin-right: 12px;
+ color: #038b8c;
+}
+
+.light .rt-view-no-nodes-icon svg {
+ color: #0033ad;
+}
+
+.light .rt-view-no-nodes-message {
+ font-size: 23px;
+ color: #0033ad;
+}
+
+.light .rt-view-charts-container {
+ padding-bottom: 0px;
+}
+
+.light .rt-view-chart-container {
+ background-color: #eeeeee;
+ padding-top: 10px;
+ padding-bottom: 20px;
+ padding-left: 20px;
+ padding-right: 20px;
+ margin-bottom: 24px;
+ border: 1px solid #dddddd;
+ border-radius: 6px;
+}
+
+.light .rt-view-chart-name {
+ color: #444;
+}
+
+.light .rt-view-about-title {
+ color: #444;
+}
+
+.light .rt-view-about-head {
+ color: whitesmoke;
+ background-color: whitesmoke;
+ border-bottom: 1px solid #bebebe;
+}
+
+.light .rt-view-about-body {
+ color: #555;
+ background-color: #eaeaea;
+ border-bottom-left-radius: 6px;
+ border-bottom-right-radius: 6px;
+}
+
+.light .rt-view-peer-title {
+ color: #444;
+}
+
+.light .rt-view-peer-head {
+ color: whitesmoke;
+ background-color: whitesmoke;
+ border-bottom: 1px solid #bebebe;
+}
+
+.light .rt-view-peer-body {
+ color: #555;
+ background-color: #eaeaea;
+ border-bottom-left-radius: 6px;
+ border-bottom-right-radius: 6px;
+}
+
+.light .rt-view-errors-title {
+ color: #444;
+}
+
+.light .rt-view-errors-head {
+ color: #555;
+ background-color: whitesmoke;
+ border-bottom: 1px solid #bebebe;
+}
+
+.light .rt-view-errors-body {
+ color: #555;
+ background-color: #eaeaea;
+ border-bottom-left-radius: 6px;
+ border-bottom-right-radius: 6px;
+}
+
+.light .rt-view-errors-foot {
+ color: #555;
+ background-color: whitesmoke;
+ border-top: 1px solid #bebebe;
+}
+
+.light .rt-view-notifications-title {
+ color: #444;
+}
+
+.light .rt-view-notifications-head {
+ color: whitesmoke;
+ background-color: whitesmoke;
+ border-bottom: 1px solid #bebebe;
+}
+
+.light .rt-view-notifications-body {
+ color: #555;
+ background-color: #eaeaea;
+ border-bottom-left-radius: 6px;
+ border-bottom-right-radius: 6px;
+}
+
+.light .rt-view-ekg-metrics-title {
+ color: #444;
+}
+
+.light .rt-view-ekg-metrics-head {
+ color: whitesmoke;
+ background-color: whitesmoke;
+ border-bottom: 1px solid #bebebe;
+}
+
+.light .rt-view-ekg-metrics-body {
+ color: #555;
+ background-color: #eaeaea;
+ border-bottom-left-radius: 6px;
+ border-bottom-right-radius: 6px;
+}
+
+.light .rt-view-main-table {
+ background-color: #f5f5f5;
+ color: #444;
+}
+
+.light .rt-view-main-table td {
+ padding-top: 17px;
+ padding-bottom: 17px;
+ border-bottom: 0px solid #444;
+}
+
+.light .rt-view-main-table th {
+ color: #444;
+ border-bottom: 2px solid #cfcfcf;
+ vertical-align: middle;
+}
+
+.light .rt-view-peer-table {
+ background-color: #eaeaea;
+ color: #444;
+}
+
+.light .rt-view-peer-table td {
+ padding-top: 10px;
+ padding-bottom: 10px;
+ border-bottom: 0px solid #444;
+}
+
+.light .rt-view-peer-table th {
+ color: #444;
+ border-bottom: 2px solid #cfcfcf;
+ vertical-align: middle;
+}
+
+.light .rt-view-errors-table {
+ background-color: #eaeaea;
+ color: #444;
+}
+
+.light .rt-view-errors-table td {
+ padding-top: 10px;
+ padding-bottom: 10px;
+ border-bottom: 0px solid #444;
+}
+
+.light .rt-view-errors-table th {
+ color: #444;
+ border-bottom: 2px solid #cfcfcf;
+ vertical-align: middle;
+}
+
+.light .rt-view-chart-group-title {
+ color: #444;
+ font-weight: bold;
+ margin-left: 13px;
+ font-size: 110%;
+}
+
+.light .rt-view-show-hide-chart-group svg {
+ width: 21px;
+ color: #038b8c;
+}
+
+.light .rt-view-chart-icon svg {
+ width: 19px;
+ margin-right: 11px;
+ color: #038b8c;
+}
+
+.light .rt-view-footer {
+ background-color: #efefef;
+ color: #777;
+ padding-top: 16px;
+ padding-bottom: 12px;
+ border-top: 1px solid #dbdbdb;
+ font-size: 80%;
+}
+
+.light .rt-view-footer-github svg {
+ width: 23px;
+ margin-top: 1px;
+ color: #038b8c;
+}
+
+.light .rt-view-percent-done {
+ color: #048b04;
+}
+
+.light .rt-view-what-icon svg {
+ width: 18px;
+ margin-left: 12px;
+ color: #9a9a9a;
+}
+
+.light .rt-view-epoch-end svg {
+ width: 16px;
+ margin-left: 20px;
+ margin-right: 5px;
+ color: #038b8c;
+}
+
+.light .rt-view-notify-menu-icon svg {
+ width: 15px;
+ padding-top: 6px;
+ margin-right: 9px;
+ color: #0033ad;
+}
+|]
+
+chartTextLight
+ , chartTextDark
+ , chartGridDark
+ , chartGridLight :: String
+chartGridDark = "#ccc"
+chartGridLight = "#555"
+chartTextDark = "#555"
+chartTextLight = "#ddd"
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/Charts.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/Charts.hs
new file mode 100644
index 00000000000..7f80b6d0106
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/Charts.hs
@@ -0,0 +1,298 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Cardano.Tracer.Handlers.RTView.UI.Charts
+ ( initColors
+ , initDatasetsIndices
+ , initDatasetsTimestamps
+ , getDatasetIx
+ , addNodeDatasetsToCharts
+ , addPointsToChart
+ , addAllPointsToChart
+ , getLatestDisplayedTS
+ , saveLatestDisplayedTS
+ , restoreChartsSettings
+ , saveChartsSettings
+ , changeChartsToLightTheme
+ , changeChartsToDarkTheme
+ ) where
+
+-- | The module 'Cardano.Tracer.Handlers.RTView.UI.JS.Charts' contains the tools
+-- for rendering/updating charts using Chart.JS library, via JS FFI.
+--
+-- This module contains common tools for charts' state. We need it to be able
+-- to re-render their values after web-page reloading.
+
+import Control.Concurrent.STM (atomically)
+import Control.Concurrent.STM.TBQueue
+import Control.Concurrent.STM.TVar
+import Control.Exception.Extra (ignore, try_)
+import Control.Monad (forM, forM_, when)
+import Data.Aeson
+import Data.List.Extra (chunksOf)
+import qualified Data.Map.Strict as M
+import Data.Maybe (catMaybes, fromMaybe)
+import qualified Data.Set as S
+import Data.Text (pack)
+import Graphics.UI.Threepenny.Core
+import Text.Read (readMaybe)
+
+import Cardano.Tracer.Types
+import Cardano.Tracer.Handlers.RTView.State.Displayed
+import Cardano.Tracer.Handlers.RTView.State.Historical
+import Cardano.Tracer.Handlers.RTView.System
+import Cardano.Tracer.Handlers.RTView.UI.CSS.Own
+import qualified Cardano.Tracer.Handlers.RTView.UI.JS.Charts as Chart
+import qualified Cardano.Tracer.Handlers.RTView.UI.JS.Utils as JS
+import Cardano.Tracer.Handlers.RTView.UI.Types
+import Cardano.Tracer.Handlers.RTView.UI.Utils
+
+chartsIds :: [ChartId]
+chartsIds = [minBound .. maxBound]
+
+initColors :: UI Colors
+initColors = liftIO $ do
+ q <- newTBQueueIO . fromIntegral $ length colors
+ mapM_ (atomically . writeTBQueue q . Color) colors
+ return q
+ where
+ -- | There are unique colors for each chart line corresponding to each connected node.
+ -- To make chart lines visually distinct, the colors in this list are contrast enough.
+ -- It is assumed that the number of colors in this list is enough.
+ colors =
+ [ "#ff0000", "#66ff33", "#0066ff", "#ff00ff", "#cc0066", "#00ccff", "#ff9933", "#cc9900"
+ , "#33cc33", "#0099ff"
+ ]
+
+getNewColor :: Colors -> UI Color
+getNewColor q =
+ (liftIO . atomically $ tryReadTBQueue q) >>= \case
+ Just color -> return color
+ Nothing -> return defaultColor
+ where
+ defaultColor = Color "#cccc00"
+
+initDatasetsIndices :: UI DatasetsIndices
+initDatasetsIndices = liftIO . newTVarIO $ M.empty
+
+saveDatasetIx
+ :: DatasetsIndices
+ -> NodeId
+ -> Index
+ -> UI ()
+saveDatasetIx indices nodeId ix = liftIO . atomically $
+ modifyTVar' indices $ \currentIndices ->
+ case M.lookup nodeId currentIndices of
+ Nothing -> M.insert nodeId ix currentIndices
+ Just _ -> M.adjust (const ix) nodeId currentIndices
+
+getDatasetIx
+ :: DatasetsIndices
+ -> NodeId
+ -> UI (Maybe Index)
+getDatasetIx indices nodeId = liftIO $
+ M.lookup nodeId <$> readTVarIO indices
+
+addNodeDatasetsToCharts
+ :: Window
+ -> NodeId
+ -> Colors
+ -> DatasetsIndices
+ -> DisplayedElements
+ -> UI ()
+addNodeDatasetsToCharts window nodeId@(NodeId anId) colors datasetIndices displayedElements = do
+ colorForNode@(Color code) <- getNewColor colors
+ forM_ chartsIds $ \chartId -> do
+ newIx <- Chart.getDatasetsLengthChartJS chartId
+ nodeName <- liftIO $ getDisplayedValue displayedElements nodeId (anId <> "__node-name")
+ Chart.addDatasetChartJS chartId (fromMaybe anId nodeName) colorForNode
+ saveDatasetIx datasetIndices nodeId (Index newIx)
+ -- Change color label for node name as well.
+ findAndSet (set style [("color", code)]) window (anId <> "__node-chart-label")
+
+initDatasetsTimestamps :: UI DatasetsTimestamps
+initDatasetsTimestamps = liftIO . newTVarIO $ M.empty
+
+saveLatestDisplayedTS
+ :: DatasetsTimestamps
+ -> NodeId
+ -> DataName
+ -> POSIXTime
+ -> UI ()
+saveLatestDisplayedTS tss nodeId dataName ts = liftIO . atomically $
+ modifyTVar' tss $ \currentTimestamps ->
+ case M.lookup nodeId currentTimestamps of
+ Nothing ->
+ -- There is no latest timestamps for charts for this node yet.
+ let newTSForNode = M.singleton dataName ts
+ in M.insert nodeId newTSForNode currentTimestamps
+ Just tssForNode ->
+ let newTSForNode =
+ case M.lookup dataName tssForNode of
+ Nothing ->
+ -- There is no latest timestamps for this dataName yet.
+ M.insert dataName ts tssForNode
+ Just _ ->
+ M.adjust (const ts) dataName tssForNode
+ in M.adjust (const newTSForNode) nodeId currentTimestamps
+
+getLatestDisplayedTS
+ :: DatasetsTimestamps
+ -> NodeId
+ -> DataName
+ -> UI (Maybe POSIXTime)
+getLatestDisplayedTS tss nodeId dataName = liftIO $ do
+ tss' <- readTVarIO tss
+ case M.lookup nodeId tss' of
+ Nothing -> return Nothing
+ Just tssForNode -> return $ M.lookup dataName tssForNode
+
+-- Each chart updates independently from others. Because of this, the user
+-- can specify "auto-update period" for each chart. Some of data (by its nature)
+-- shoudn't be updated too frequently.
+--
+-- All points will be added to all datasets (corresponding to the number of connected nodes)
+-- using one single FFI-call, for better performance.
+--
+-- 'addAllPointsToChart' doesn not do average calculation, it pushes all the points as they are.
+addPointsToChart, addAllPointsToChart
+ :: ConnectedNodes
+ -> History
+ -> DatasetsIndices
+ -> DatasetsTimestamps
+ -> DataName
+ -> ChartId
+ -> UI ()
+addPointsToChart = doAddPointsToChart replacePointsByAvgPoints
+addAllPointsToChart = doAddPointsToChart id
+
+doAddPointsToChart
+ :: ([HistoricalPoint] -> [HistoricalPoint])
+ -> ConnectedNodes
+ -> History
+ -> DatasetsIndices
+ -> DatasetsTimestamps
+ -> DataName
+ -> ChartId
+ -> UI ()
+doAddPointsToChart replaceByAvg connectedNodes hist datasetIndices datasetTimestamps dataName chartId = do
+ connected <- liftIO $ S.toList <$> readTVarIO connectedNodes
+ dataForPush <-
+ forM connected $ \nodeId ->
+ liftIO (getHistoricalData hist nodeId dataName) >>= \case
+ [] -> return Nothing
+ points -> do
+ let (latestTS, _) = last points
+ getLatestDisplayedTS datasetTimestamps nodeId dataName >>= \case
+ Nothing ->
+ -- There is no saved latestTS for this node and chart yet,
+ -- so display all the history and remember the latestTS.
+ getDatasetIx datasetIndices nodeId >>= \case
+ Nothing -> return Nothing
+ Just ix ->
+ return . Just $ ( (nodeId, latestTS)
+ , (ix, replaceByAvg points)
+ )
+ Just storedTS ->
+ -- Some of the history for this node and chart is already displayed,
+ -- so cut displayed points first. The only points we should add now
+ -- are the points with 'ts' that is bigger than 'storedTS'.
+ getDatasetIx datasetIndices nodeId >>= \case
+ Nothing -> return Nothing
+ Just ix ->
+ return . Just $ ( (nodeId, latestTS)
+ , (ix, replaceByAvg $! cutOldPoints storedTS points)
+ )
+ let (nodeIdsWithLatestTss, datasetIxsWithPoints) = unzip $ catMaybes dataForPush
+ Chart.addAllPointsChartJS chartId datasetIxsWithPoints
+ forM_ nodeIdsWithLatestTss $ \(nodeId, latestTS) ->
+ saveLatestDisplayedTS datasetTimestamps nodeId dataName latestTS
+
+replacePointsByAvgPoints :: [HistoricalPoint] -> [HistoricalPoint]
+replacePointsByAvgPoints [] = []
+replacePointsByAvgPoints points =
+ map calculateAvgPoint $ chunksOf numberOfPointsToAverage points
+ where
+ calculateAvgPoint :: [HistoricalPoint] -> HistoricalPoint
+ calculateAvgPoint pointsForAvg =
+ let !valuesSum = sum [v | (_, v) <- pointsForAvg]
+ avgValue =
+ case valuesSum of
+ ValueI i -> ValueD $ fromIntegral i / fromIntegral (length pointsForAvg)
+ ValueD d -> ValueD $ d / fromIntegral (length pointsForAvg)
+ (latestTS, _) = last pointsForAvg
+ in (latestTS, avgValue)
+ -- TODO: calculate it, remove hardcoded value!
+ -- P1 - Period of calling 'addAllPointsToChart'
+ -- P2 - Period of asking of EKG.Metrics
+ -- Number of new points received since last call of 'addAllPointsToChart'
+ -- is P1/P2
+ -- Maximum number of points to calculate avg = 15 s.
+ numberOfPointsToAverage = 15
+
+cutOldPoints
+ :: POSIXTime
+ -> [HistoricalPoint]
+ -> [HistoricalPoint]
+cutOldPoints _ [] = []
+cutOldPoints oldTS (point@(ts, _):newerPoints) =
+ if ts > oldTS
+ then
+ -- This point is newer than 'oldTS', take it and all the following
+ -- as well, because they are definitely newer (points are sorted by ts).
+ point : newerPoints
+ else
+ -- This point are older than 'oldTS', it means that it already was displayed.
+ cutOldPoints oldTS newerPoints
+
+restoreChartsSettings :: UI ()
+restoreChartsSettings = readSavedChartsSettings >>= setCharts
+ where
+ setCharts settings =
+ forM_ settings $ \(chartId, ChartSettings tr up) -> do
+ JS.selectOption (show chartId <> show TimeRangeSelect) tr
+ JS.selectOption (show chartId <> show UpdatePeriodSelect) up
+ Chart.setTimeRange chartId tr
+ when (tr == 0) $ Chart.resetZoomChartJS chartId
+
+saveChartsSettings :: Window -> UI ()
+saveChartsSettings window = do
+ settings <-
+ forM chartsIds $ \chartId -> do
+ selectedTR <- getOptionValue $ show chartId <> show TimeRangeSelect
+ selectedUP <- getOptionValue $ show chartId <> show UpdatePeriodSelect
+ return (chartId, ChartSettings selectedTR selectedUP)
+ liftIO . ignore $ do
+ pathToChartsConfig <- getPathToChartsConfig
+ encodeFile pathToChartsConfig settings
+ where
+ getOptionValue selectId = do
+ v <- findAndGetValue window (pack selectId)
+ case readMaybe v of
+ Just (valueInS :: Int) -> return valueInS
+ Nothing -> return 0
+
+readSavedChartsSettings :: UI ChartsSettings
+readSavedChartsSettings = liftIO $
+ try_ (decodeFileStrict' =<< getPathToChartsConfig) >>= \case
+ Right (Just (settings :: ChartsSettings)) -> return settings
+ _ -> return defaultSettings
+ where
+ defaultSettings =
+ [ (chartId, ChartSettings defaultTimeRangeInS defaultUpdatePeriodInS)
+ | chartId <- chartsIds
+ ]
+ defaultTimeRangeInS = 0 -- All time
+ defaultUpdatePeriodInS = 15
+
+changeChartsToLightTheme :: UI ()
+changeChartsToLightTheme =
+ forM_ chartsIds $ \chartId ->
+ Chart.changeColorsChartJS chartId (Color chartTextDark) (Color chartGridDark)
+
+changeChartsToDarkTheme :: UI ()
+changeChartsToDarkTheme =
+ forM_ chartsIds $ \chartId ->
+ Chart.changeColorsChartJS chartId (Color chartTextLight) (Color chartGridLight)
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/About.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/About.hs
new file mode 100644
index 00000000000..3c9d46fb9ad
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/About.hs
@@ -0,0 +1,103 @@
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE MultiWayIf #-}
+
+module Cardano.Tracer.Handlers.RTView.UI.HTML.About
+ ( mkAboutInfo
+ ) where
+
+import qualified Data.Text as T
+import Data.Version (showVersion)
+import qualified Graphics.UI.Threepenny as UI
+import Graphics.UI.Threepenny.Core
+import System.Directory (makeAbsolute)
+import System.Environment (getArgs)
+import System.Info.Extra (isWindows, isMac)
+
+import Cardano.Git.Rev (gitRev)
+
+import Cardano.Tracer.Handlers.RTView.System
+import Cardano.Tracer.Handlers.RTView.UI.JS.Utils
+import Cardano.Tracer.Handlers.RTView.UI.Img.Icons
+import Cardano.Tracer.Handlers.RTView.UI.Utils
+import Paths_cardano_tracer (version)
+
+mkAboutInfo :: UI Element
+mkAboutInfo = do
+ pathToConfig <- liftIO $ getArgs >>= \case
+ ["-c", path] -> makeAbsolute path
+ ["--config", path] -> makeAbsolute path
+ _ -> return ""
+ copyPath <- image "has-tooltip-multiline has-tooltip-top rt-view-copy-icon" copySVG
+ # set dataTooltip "Click to copy the path"
+ on UI.click copyPath . const $
+ copyTextToClipboard pathToConfig
+ closeIt <- UI.button #. "delete"
+ pid <- getProcessId
+ info <-
+ UI.div #. "modal" #+
+ [ UI.div #. "modal-background" #+ []
+ , UI.div #. "modal-card" #+
+ [ UI.header #. "modal-card-head rt-view-about-head" #+
+ [ UI.p #. "modal-card-title rt-view-about-title" # set text "About"
+ , element closeIt
+ ]
+ , UI.mkElement "section" #. "modal-card-body rt-view-about-body" #+
+ [ UI.div #. "columns" #+
+ [ UI.div #. "column ml-1 is-one-third" #+
+ [ UI.p #. "mb-3" #+
+ [ image "rt-view-overview-icon" versionSVG
+ , string "Version"
+ ]
+ , UI.p #. "mb-3" #+
+ [ image "rt-view-overview-icon" commitSVG
+ , string "Commit"
+ ]
+ , UI.p #. "mb-3" #+
+ [ image "rt-view-overview-icon" platformSVG
+ , string "Platform"
+ ]
+ , UI.p #. "mb-3" #+
+ [ image "rt-view-overview-icon" configSVG
+ , string "Configuration"
+ ]
+ , UI.p #. "mb-1" #+
+ [ image "rt-view-overview-icon" serverSVG
+ , string "Process ID"
+ ]
+ ]
+ , UI.div #. "column has-text-weight-semibold" #+
+ [ UI.p #. "mb-3" #+ [string $ showVersion version]
+ , UI.p #. "mb-3" #+
+ [ UI.anchor
+ #. ("rt-view-href is-family-monospace has-text-weight-normal"
+ <> " has-tooltip-multiline has-tooltip-top")
+ # set UI.href ("https://github.com/input-output-hk/cardano-node/commit/" <> commit)
+ # set UI.target "_blank"
+ # set dataTooltip "Browse repository on this commit"
+ # set text commit
+ , image "rt-view-href-icon" externalLinkSVG
+ ]
+ , UI.p #. "mb-3" #+
+ [ string $ if | isWindows -> "Windows"
+ | isMac -> "macOS"
+ | otherwise -> "Linux"
+ ]
+ , UI.p #. "mb-3" #+
+ [ UI.span #. ("tag is-info is-light is-rounded is-medium mr-3"
+ <> " has-tooltip-multiline has-tooltip-top rt-view-logs-path")
+ # set dataTooltip "The path to configuration file"
+ # set text (shortenPath pathToConfig)
+ , element copyPath
+ ]
+ , UI.p #. "mb-1" #+
+ [ string $ show pid
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ on UI.click closeIt . const $ element info #. "modal"
+ return info
+ where
+ commit = T.unpack . T.take 7 $ gitRev
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Body.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Body.hs
new file mode 100644
index 00000000000..e504e8033e0
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Body.hs
@@ -0,0 +1,687 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Cardano.Tracer.Handlers.RTView.UI.HTML.Body
+ ( mkPageBody
+ ) where
+
+import Control.Monad (void, unless, when)
+import Control.Monad.Extra (whenM, whenJustM)
+import Data.Text (Text)
+import qualified Graphics.UI.Threepenny as UI
+import Graphics.UI.Threepenny.Core
+import Graphics.UI.Threepenny.JQuery
+import Text.Read (readMaybe)
+
+import Cardano.Tracer.Configuration
+import Cardano.Tracer.Handlers.RTView.State.Historical
+import Cardano.Tracer.Handlers.RTView.UI.Img.Icons
+import Cardano.Tracer.Handlers.RTView.UI.HTML.About
+import Cardano.Tracer.Handlers.RTView.UI.HTML.NoNodes
+import Cardano.Tracer.Handlers.RTView.UI.HTML.Notifications
+import qualified Cardano.Tracer.Handlers.RTView.UI.JS.Charts as Chart
+import Cardano.Tracer.Handlers.RTView.UI.JS.ChartJS
+import Cardano.Tracer.Handlers.RTView.UI.JS.Utils
+import Cardano.Tracer.Handlers.RTView.UI.Charts
+import Cardano.Tracer.Handlers.RTView.UI.Theme
+import Cardano.Tracer.Handlers.RTView.UI.Types
+import Cardano.Tracer.Handlers.RTView.UI.Utils
+import Cardano.Tracer.Types
+
+mkPageBody
+ :: UI.Window
+ -> Network
+ -> ConnectedNodes
+ -> ResourcesHistory
+ -> BlockchainHistory
+ -> TransactionsHistory
+ -> DatasetsIndices
+ -> DatasetsTimestamps
+ -> UI Element
+mkPageBody window networkConfig connected
+ (ResHistory rHistory) (ChainHistory cHistory) (TXHistory tHistory)
+ dsIxs dsTss = do
+ txsProcessedNumTimer <- mkChartTimer connected tHistory dsIxs dsTss TxsProcessedNumData TxsProcessedNumChart
+ mempoolBytesTimer <- mkChartTimer connected tHistory dsIxs dsTss MempoolBytesData MempoolBytesChart
+ txsInMempoolTimer <- mkChartTimer connected tHistory dsIxs dsTss TxsInMempoolData TxsInMempoolChart
+
+ txsProcessedNumChart <- mkChart window txsProcessedNumTimer TxsProcessedNumChart "Processed txs"
+ mempoolBytesChart <- mkChart window mempoolBytesTimer MempoolBytesChart "Mempool size"
+ txsInMempoolChart <- mkChart window txsInMempoolTimer TxsInMempoolChart "Txs in mempool"
+
+ -- Resources charts.
+ cpuTimer <- mkChartTimer connected rHistory dsIxs dsTss CPUData CPUChart
+ memoryTimer <- mkChartTimer connected rHistory dsIxs dsTss MemoryData MemoryChart
+ gcMajorNumTimer <- mkChartTimer connected rHistory dsIxs dsTss GCMajorNumData GCMajorNumChart
+ gcMinorNumTimer <- mkChartTimer connected rHistory dsIxs dsTss GCMinorNumData GCMinorNumChart
+ gcLiveMemoryTimer <- mkChartTimer connected rHistory dsIxs dsTss GCLiveMemoryData GCLiveMemoryChart
+ cpuTimeGCTimer <- mkChartTimer connected rHistory dsIxs dsTss CPUTimeGCData CPUTimeGCChart
+ cpuTimeAppTimer <- mkChartTimer connected rHistory dsIxs dsTss CPUTimeAppData CPUTimeAppChart
+ threadsNumTimer <- mkChartTimer connected rHistory dsIxs dsTss ThreadsNumData ThreadsNumChart
+
+ cpuChart <- mkChart window cpuTimer CPUChart "CPU usage"
+ memoryChart <- mkChart window memoryTimer MemoryChart "Memory usage"
+ gcMajorNumChart <- mkChart window gcMajorNumTimer GCMajorNumChart "Number of major GCs"
+ gcMinorNumChart <- mkChart window gcMinorNumTimer GCMinorNumChart "Number of minor GCs"
+ gcLiveMemoryChart <- mkChart window gcLiveMemoryTimer GCLiveMemoryChart "GC, live data in heap"
+ cpuTimeGCChart <- mkChart window cpuTimeGCTimer CPUTimeGCChart "CPU time used by GC"
+ cpuTimeAppChart <- mkChart window cpuTimeAppTimer CPUTimeAppChart "CPU time used by app"
+ threadsNumChart <- mkChart window threadsNumTimer ThreadsNumChart "Number of threads"
+
+ -- Blockchain charts.
+ chainDensityTimer <- mkChartTimer connected cHistory dsIxs dsTss ChainDensityData ChainDensityChart
+ slotNumTimer <- mkChartTimer connected cHistory dsIxs dsTss SlotNumData SlotNumChart
+ blockNumTimer <- mkChartTimer connected cHistory dsIxs dsTss BlockNumData BlockNumChart
+ slotInEpochTimer <- mkChartTimer connected cHistory dsIxs dsTss SlotInEpochData SlotInEpochChart
+ epochTimer <- mkChartTimer connected cHistory dsIxs dsTss EpochData EpochChart
+
+ chainDensityChart <- mkChart window chainDensityTimer ChainDensityChart "Chain density"
+ slotNumChart <- mkChart window slotNumTimer SlotNumChart "Slot height"
+ blockNumChart <- mkChart window blockNumTimer BlockNumChart "Block height"
+ slotInEpochChart <- mkChart window slotInEpochTimer SlotInEpochChart "Slot in epoch"
+ epochChart <- mkChart window epochTimer EpochChart "Epoch"
+
+ -- Leadership charts.
+ cannotForgeTimer <- mkChartTimer' connected cHistory dsIxs dsTss NodeCannotForgeData NodeCannotForgeChart
+ forgedSlotTimer <- mkChartTimer' connected cHistory dsIxs dsTss ForgedSlotLastData ForgedSlotLastChart
+ nodeIsLeaderTimer <- mkChartTimer' connected cHistory dsIxs dsTss NodeIsLeaderData NodeIsLeaderChart
+ nodeIsNotLeaderTimer <- mkChartTimer' connected cHistory dsIxs dsTss NodeIsNotLeaderData NodeIsNotLeaderChart
+ forgedInvalidTimer <- mkChartTimer' connected cHistory dsIxs dsTss ForgedInvalidSlotLastData ForgedInvalidSlotLastChart
+ adoptedTimer <- mkChartTimer' connected cHistory dsIxs dsTss AdoptedSlotLastData AdoptedSlotLastChart
+ notAdoptedTimer <- mkChartTimer' connected cHistory dsIxs dsTss NotAdoptedSlotLastData NotAdoptedSlotLastChart
+ aboutToLeadTimer <- mkChartTimer' connected cHistory dsIxs dsTss AboutToLeadSlotLastData AboutToLeadSlotLastChart
+ couldNotForgeTimer <- mkChartTimer' connected cHistory dsIxs dsTss CouldNotForgeSlotLastData CouldNotForgeSlotLastChart
+
+ cannotForgeChart <- mkChart window cannotForgeTimer NodeCannotForgeChart "Cannot forge"
+ forgedSlotChart <- mkChart window forgedSlotTimer ForgedSlotLastChart "Forged"
+ nodeIsLeaderChart <- mkChart window nodeIsLeaderTimer NodeIsLeaderChart "Is leader"
+ nodeIsNotLeaderChart <- mkChart window nodeIsNotLeaderTimer NodeIsNotLeaderChart "Is not leader"
+ forgedInvalidChart <- mkChart window forgedInvalidTimer ForgedInvalidSlotLastChart "Forged invalid"
+ adoptedChart <- mkChart window adoptedTimer AdoptedSlotLastChart "Is adopted"
+ notAdoptedChart <- mkChart window notAdoptedTimer NotAdoptedSlotLastChart "Is not adopted"
+ aboutToLeadChart <- mkChart window aboutToLeadTimer AboutToLeadSlotLastChart "About to lead"
+ couldNotForgeChart <- mkChart window couldNotForgeTimer CouldNotForgeSlotLastChart "Could not forge"
+
+ -- Visibility of charts groups.
+ showHideTxs <- image "has-tooltip-multiline has-tooltip-top rt-view-show-hide-chart-group" showSVG
+ # set dataTooltip "Click to hide Transactions"
+ # set dataState shownState
+ showHideChain <- image "has-tooltip-multiline has-tooltip-top rt-view-show-hide-chart-group" showSVG
+ # set dataTooltip "Click to hide Blockchain"
+ # set dataState shownState
+ showHideLeadership <- image "has-tooltip-multiline has-tooltip-top rt-view-show-hide-chart-group" showSVG
+ # set dataTooltip "Click to hide Leadership"
+ # set dataState shownState
+ showHideResources <- image "has-tooltip-multiline has-tooltip-top rt-view-show-hide-chart-group" showSVG
+ # set dataTooltip "Click to hide Resources"
+ # set dataState shownState
+
+ on UI.click showHideTxs . const $
+ changeVisibilityForCharts window showHideTxs "transactions-charts" "Transactions"
+ on UI.click showHideChain . const $
+ changeVisibilityForCharts window showHideChain "chain-charts" "Blockchain"
+ on UI.click showHideLeadership . const $
+ changeVisibilityForCharts window showHideLeadership "leadership-charts" "Leadership"
+ on UI.click showHideResources . const $
+ changeVisibilityForCharts window showHideResources "resources-charts" "Resources"
+
+ -- Body.
+ body <-
+ UI.getBody window #+
+ [ UI.div #. "wrapper" #+
+ [ UI.div ## "preloader" #. "pageloader is-active" #+
+ [ UI.span #. "title" # set text "Just a second..."
+ ]
+ , topNavigation window
+ , UI.div ## "no-nodes" #. "container is-max-widescreen has-text-centered" #+
+ [ image "rt-view-no-nodes-icon" noNodesSVG ## "no-nodes-icon"
+ , UI.p ## "no-nodes-message" #. "rt-view-no-nodes-message" #+
+ [ string "There are no connected nodes. Yet."
+ ]
+ ]
+ , mkNoNodesInfo networkConfig
+ , UI.mkElement "section" #. "section" #+
+ [ UI.div ## "main-table-container"
+ #. "table-container"
+ # hideIt #+
+ [ UI.table ## "main-table" #. "table rt-view-main-table" #+
+ [ UI.mkElement "thead" #+
+ [ UI.tr ## "node-name-row" #+
+ [ UI.th #. "rt-view-main-table-description"
+ #+ [UI.span # set html " "]
+ ]
+ ]
+ , UI.mkElement "tbody" #+
+ [ UI.tr ## "node-version-row" #+
+ [ UI.td #+ [ image "rt-view-overview-icon" versionSVG
+ , string "Version"
+ ]
+ ]
+ , UI.tr ## "node-commit-row" #+
+ [ UI.td #+ [ image "rt-view-overview-icon" commitSVG
+ , string "Commit"
+ ]
+ ]
+ , UI.tr ## "node-protocol-row" #+
+ [ UI.td #+ [ image "rt-view-overview-icon" protocolSVG
+ , string "Protocol"
+ ]
+ ]
+ , UI.tr ## "node-era-row" #+
+ [ UI.td #+ [ image "rt-view-overview-icon" eraSVG
+ , string "Era"
+ ]
+ ]
+ , UI.tr ## "node-epoch-row" #+
+ [ UI.td #+ [ image "rt-view-overview-icon" lengthSVG
+ , string "Epoch"
+ , image "has-tooltip-multiline has-tooltip-right rt-view-what-icon" whatSVG
+ # set dataTooltip ("Current epoch from this node."
+ <> " It can be outdated because of node's out of sync!")
+ ]
+ ]
+ , UI.tr ## "node-sync-row" #+
+ [ UI.td #+ [ image "rt-view-overview-icon" refreshSVG
+ , string "Sync"
+ ]
+ ]
+ , UI.tr ## "node-system-start-time-row" #+
+ [ UI.td #+ [ image "rt-view-overview-icon" systemStartSVG
+ , string "Blockchain start"
+ ]
+ ]
+ , UI.tr ## "node-start-time-row" #+
+ [ UI.td #+ [ image "rt-view-overview-icon" startSVG
+ , string "Node start"
+ ]
+ ]
+ , UI.tr ## "node-uptime-row" #+
+ [ UI.td #+ [ image "rt-view-overview-icon" uptimeSVG
+ , string "Uptime"
+ ]
+ ]
+ , UI.tr ## "node-logs-row" #+
+ [ UI.td #+ [ image "rt-view-overview-icon" logsSVG
+ , string "Logs"
+ ]
+ ]
+ , UI.tr ## "node-block-replay-row" #+
+ [ UI.td #+ [ image "rt-view-overview-icon" blocksSVG
+ , string "Block replay"
+ ]
+ ]
+ , UI.tr ## "node-chunk-validation-row" #+
+ [ UI.td #+ [ image "rt-view-overview-icon" dbSVG
+ , string "Chunk validation"
+ ]
+ ]
+ , UI.tr ## "node-update-ledger-db-row" #+
+ [ UI.td #+ [ image "rt-view-overview-icon" dbSVG
+ , string "Ledger DB"
+ ]
+ ]
+ , UI.tr ## "node-peers-row" #+
+ [ UI.td #+ [ image "rt-view-overview-icon" peersSVG
+ , string "Peers"
+ ]
+ ]
+ , UI.tr ## "node-errors-row" #+
+ [ UI.td #+ [ image "rt-view-overview-icon" errorsSVG
+ , string "Errors"
+ ]
+ ]
+ , UI.tr ## "node-leadership-row" #+
+ [ UI.td #+ [ image "rt-view-overview-icon" leaderSVG
+ , string "Leadership"
+ , image "has-tooltip-multiline has-tooltip-right rt-view-what-icon" whatSVG
+ # set dataTooltip "How many times this node was leader"
+ ]
+ ]
+ , UI.tr ## "node-forged-blocks-row" #+
+ [ UI.td #+ [ image "rt-view-overview-icon" forgeSVG
+ , string "Forged blocks"
+ , image "has-tooltip-multiline has-tooltip-right rt-view-what-icon" whatSVG
+ # set dataTooltip "How many blocks did forge by this node"
+ ]
+ ]
+ , UI.tr ## "node-cannot-forge-row" #+
+ [ UI.td #+ [ image "rt-view-overview-icon" notForgeSVG
+ , string "Cannot forge"
+ , image "has-tooltip-multiline has-tooltip-right rt-view-what-icon" whatSVG
+ # set dataTooltip "How many times this node could not forge"
+ ]
+ ]
+ , UI.tr ## "node-missed-slots-row" #+
+ [ UI.td #+ [ image "rt-view-overview-icon" missedSVG
+ , string "Missed slots"
+ , image "has-tooltip-multiline has-tooltip-right rt-view-what-icon" whatSVG
+ # set dataTooltip "How many slots were missed by this node"
+ ]
+ ]
+ , UI.tr ## "node-current-kes-period-row" #+
+ [ UI.td #+ [ image "rt-view-overview-icon" certificateSVG
+ , string "KES current"
+ ]
+ ]
+ , UI.tr ## "node-op-cert-expiry-kes-period-row" #+
+ [ UI.td #+ [ image "rt-view-overview-icon" certificateSVG
+ , string "KES Expiry"
+ ]
+ ]
+ , UI.tr ## "node-remaining-kes-periods-row" #+
+ [ UI.td #+ [ image "rt-view-overview-icon" certificateSVG
+ , string "Remainig KES"
+ ]
+ ]
+ , UI.tr ## "node-op-cert-start-kes-period-row" #+
+ [ UI.td #+ [ image "rt-view-overview-icon" certificateSVG
+ , string "Op Cert Start KES"
+ ]
+ ]
+ , UI.tr ## "node-days-until-op-cert-renew-row" #+
+ [ UI.td #+ [ image "rt-view-overview-icon" endSVG
+ , string "Days until Op Cert renew"
+ ]
+ ]
+ , UI.tr ## "node-ekg-metrics-row" #+
+ [ UI.td #+ [ image "rt-view-overview-icon" ekgMetricsSVG
+ , string "EKG metrics"
+ , image "has-tooltip-multiline has-tooltip-right rt-view-what-icon" whatSVG
+ # set dataTooltip ("All EKG metrics forwarded by this node, "
+ <> "as they are (no preparing)")
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ , UI.mkElement "section" #. "section" #+
+ [ UI.div ## "main-charts-container"
+ #. "is-fluid rt-view-charts-container"
+ # hideIt #+
+ [ UI.p #. "mb-5" #+
+ [ UI.div #. "divider" #+
+ [ element showHideChain
+ , UI.span #. "rt-view-chart-group-title" # set text "Blockchain"
+ ]
+ ]
+ , UI.div ## "chain-charts" #. "columns" #+
+ [ UI.div #. "column" #+
+ [ element chainDensityChart
+ , element epochChart
+ , element blockNumChart
+ ]
+ , UI.div #. "column" #+
+ [ element slotInEpochChart
+ , element slotNumChart
+ ]
+ ]
+ -- Leadership charts.
+ , UI.p #. "mb-5" #+
+ [ UI.div #. "divider" #+
+ [ element showHideLeadership
+ , UI.span #. "rt-view-chart-group-title" # set text "Leadership"
+ ]
+ ]
+ , UI.div ## "leadership-charts" #. "columns" #+
+ [ UI.div #. "column" #+
+ [ element forgedSlotChart
+ , element nodeIsLeaderChart
+ , element forgedInvalidChart
+ , element adoptedChart
+ , element aboutToLeadChart
+ ]
+ , UI.div #. "column" #+
+ [ element cannotForgeChart
+ , element nodeIsNotLeaderChart
+ , element couldNotForgeChart
+ , element notAdoptedChart
+ ]
+ ]
+ -- Transactions charts.
+ , UI.p #. "mb-5" #+
+ [ UI.div #. "divider" #+
+ [ element showHideTxs
+ , UI.span #. "rt-view-chart-group-title" # set text "Transactions"
+ ]
+ ]
+ , UI.div ## "transactions-charts" #. "columns" #+
+ [ UI.div #. "column" #+
+ [ element mempoolBytesChart
+ , element txsProcessedNumChart
+ ]
+ , UI.div #. "column" #+
+ [ element txsInMempoolChart
+ ]
+ ]
+ -- Resources charts.
+ , UI.p #. "mb-5" #+
+ [ UI.div #. "divider" #+
+ [ element showHideResources
+ , UI.span #. "rt-view-chart-group-title" # set text "Resources"
+ ]
+ ]
+ , UI.div ## "resources-charts" #. "columns" #+
+ [ UI.div #. "column" #+
+ [ element cpuChart
+ , element gcMajorNumChart
+ , element gcLiveMemoryChart
+ , element cpuTimeGCChart
+ ]
+ , UI.div #. "column" #+
+ [ element memoryChart
+ , element gcMinorNumChart
+ , element threadsNumChart
+ , element cpuTimeAppChart
+ ]
+ ]
+ ]
+ ]
+ , footer
+ ]
+ -- JS
+ , UI.mkElement "script" # set UI.html chartJS
+ , UI.mkElement "script" # set UI.html chartJSLuxon
+ , UI.mkElement "script" # set UI.html chartJSAdapter
+ , UI.mkElement "script" # set UI.html chartJSPluginZoom
+ ]
+
+ closeModalsByEscapeButton
+
+ Chart.prepareChartsJS
+
+ Chart.newTimeChartJS TxsProcessedNumChart ""
+ Chart.newTimeChartJS MempoolBytesChart "MB"
+ Chart.newTimeChartJS TxsInMempoolChart ""
+
+ Chart.newTimeChartJS CPUChart "Percent"
+ Chart.newTimeChartJS MemoryChart "MB"
+ Chart.newTimeChartJS GCMajorNumChart ""
+ Chart.newTimeChartJS GCMinorNumChart ""
+ Chart.newTimeChartJS GCLiveMemoryChart "MB"
+ Chart.newTimeChartJS CPUTimeGCChart "Milliseconds"
+ Chart.newTimeChartJS CPUTimeAppChart "Milliseconds"
+ Chart.newTimeChartJS ThreadsNumChart ""
+
+ Chart.newTimeChartJS ChainDensityChart "Percent"
+ Chart.newTimeChartJS SlotNumChart ""
+ Chart.newTimeChartJS BlockNumChart ""
+ Chart.newTimeChartJS SlotInEpochChart ""
+ Chart.newTimeChartJS EpochChart ""
+
+ Chart.newTimeChartJS NodeCannotForgeChart "Slots"
+ Chart.newTimeChartJS ForgedSlotLastChart "Slots"
+ Chart.newTimeChartJS NodeIsLeaderChart "Slots"
+ Chart.newTimeChartJS NodeIsNotLeaderChart "Slots"
+ Chart.newTimeChartJS ForgedInvalidSlotLastChart "Slots"
+ Chart.newTimeChartJS AdoptedSlotLastChart "Slots"
+ Chart.newTimeChartJS NotAdoptedSlotLastChart "Slots"
+ Chart.newTimeChartJS AboutToLeadSlotLastChart "Slots"
+ Chart.newTimeChartJS CouldNotForgeSlotLastChart "Slots"
+
+ -- Start all timer.
+
+ UI.start txsProcessedNumTimer
+ UI.start mempoolBytesTimer
+ UI.start txsInMempoolTimer
+
+ UI.start cpuTimer
+ UI.start memoryTimer
+ UI.start gcMajorNumTimer
+ UI.start gcMinorNumTimer
+ UI.start gcLiveMemoryTimer
+ UI.start cpuTimeGCTimer
+ UI.start cpuTimeAppTimer
+ UI.start threadsNumTimer
+
+ UI.start chainDensityTimer
+ UI.start slotNumTimer
+ UI.start blockNumTimer
+ UI.start slotInEpochTimer
+ UI.start epochTimer
+
+ UI.start cannotForgeTimer
+ UI.start forgedSlotTimer
+ UI.start nodeIsLeaderTimer
+ UI.start nodeIsNotLeaderTimer
+ UI.start forgedInvalidTimer
+ UI.start adoptedTimer
+ UI.start notAdoptedTimer
+ UI.start aboutToLeadTimer
+ UI.start couldNotForgeTimer
+
+ on UI.disconnect window . const $ do
+ UI.stop txsProcessedNumTimer
+ UI.stop mempoolBytesTimer
+ UI.stop txsInMempoolTimer
+
+ UI.stop cpuTimer
+ UI.stop memoryTimer
+ UI.stop gcMajorNumTimer
+ UI.stop gcMinorNumTimer
+ UI.stop gcLiveMemoryTimer
+ UI.stop cpuTimeGCTimer
+ UI.stop cpuTimeAppTimer
+ UI.stop threadsNumTimer
+
+ UI.stop chainDensityTimer
+ UI.stop slotNumTimer
+ UI.stop blockNumTimer
+ UI.stop slotInEpochTimer
+ UI.stop epochTimer
+
+ UI.stop cannotForgeTimer
+ UI.stop forgedSlotTimer
+ UI.stop nodeIsLeaderTimer
+ UI.stop nodeIsNotLeaderTimer
+ UI.stop forgedInvalidTimer
+ UI.stop adoptedTimer
+ UI.stop notAdoptedTimer
+ UI.stop aboutToLeadTimer
+ UI.stop couldNotForgeTimer
+
+ return body
+
+topNavigation :: UI.Window -> UI Element
+topNavigation window = do
+ info <- mkAboutInfo
+ infoIcon <- image "has-tooltip-multiline has-tooltip-bottom rt-view-info-icon mr-3" rtViewInfoSVG
+ ## "info-icon"
+ # set dataTooltip "RTView info"
+ on UI.click infoIcon . const $ fadeInModal info
+
+ notificationsEvents <- mkNotificationsEvents
+ notificationsSettings <- mkNotificationsSettings
+
+ notificationsEventsItem <- UI.anchor #. "navbar-item" #+
+ [ image "rt-view-notify-menu-icon" eventsSVG
+ , string "Events"
+ ]
+ notificationsSettingsItem <- UI.anchor #. "navbar-item" #+
+ [ image "rt-view-notify-menu-icon" settingsSVG
+ , string "Settings"
+ ]
+ on UI.click notificationsEventsItem . const $ fadeInModal notificationsEvents
+ on UI.click notificationsSettingsItem . const $ fadeInModal notificationsSettings
+
+ _notificationsIcon <- image "rt-view-info-icon mr-2" rtViewNotifySVG
+ ## "notifications-icon"
+
+ themeIcon <- image "has-tooltip-multiline has-tooltip-bottom rt-view-theme-icon" rtViewThemeToLightSVG
+ ## "theme-icon"
+ # set dataTooltip "Switch to light theme"
+ on UI.click themeIcon . const $ switchTheme window
+
+ UI.div ## "top-bar" #. "navbar rt-view-top-bar" #+
+ [ element info
+ --, element notificationsEvents
+ --, element notificationsSettings
+ , UI.div #. "navbar-brand" #+
+ [ UI.div #. "navbar-item" #+
+ [ image "rt-view-cardano-logo" cardanoLogoSVG ## "cardano-logo"
+ , UI.span ## "name" #. "rt-view-name" # set text "Node Real-time View"
+ ]
+ ]
+ , UI.div #. "navbar-menu" #+
+ [ UI.div #. "navbar-start" #+ []
+ , UI.div #. "navbar-end" #+
+ [ UI.div #. "navbar-item" #+ [element themeIcon]
+ , UI.div #. "navbar-item" #+ [element infoIcon]
+ --, UI.div #. "navbar-item has-dropdown is-hoverable" #+
+ -- [ UI.anchor #. "navbar-link" #+ [element notificationsIcon]
+ -- , UI.div #. "navbar-dropdown is-right" #+
+ -- [ element notificationsEventsItem
+ -- , element notificationsSettingsItem
+ -- ]
+ -- ]
+ ]
+ ]
+ ]
+
+footer :: UI Element
+footer =
+ UI.mkElement "footer" #. "footer rt-view-footer" #+
+ [ UI.div #. "columns" #+
+ [ UI.div #. "column" #+
+ [ string "© IOHK 2015—2022"
+ ]
+ , UI.div #. "column has-text-right" #+
+ [ UI.anchor # set UI.href "https://github.com/input-output-hk/cardano-node/blob/master/cardano-tracer/README.md"
+ # set UI.target "_blank" #+
+ [ image "has-tooltip-multiline has-tooltip-left rt-view-footer-github" githubSVG
+ # set dataTooltip "Browse our GitHub repository"
+ ]
+ ]
+ ]
+ ]
+
+mkChart
+ :: UI.Window
+ -> UI.Timer
+ -> ChartId
+ -> String
+ -> UI Element
+mkChart window chartUpdateTimer chartId chartName = do
+ selectTimeRange <-
+ UI.select ## (show chartId <> show TimeRangeSelect) #+
+ -- Values are ranges in seconds.
+ [ UI.option # set value "0" # set text "All time"
+ , UI.option # set value "300" # set text "Last 5 minutes"
+ , UI.option # set value "900" # set text "Last 15 minutes"
+ , UI.option # set value "1800" # set text "Last 30 minutes"
+ , UI.option # set value "3600" # set text "Last 1 hour"
+ , UI.option # set value "10800" # set text "Last 3 hours"
+ , UI.option # set value "21600" # set text "Last 6 hours"
+ ]
+ selectUpdatePeriod <-
+ UI.select ## (show chartId <> show UpdatePeriodSelect) #+
+ -- Values are periods in seconds.
+ [ UI.option # set value "0" # set text "Off"
+ , UI.option # set value "15" # set text "15 seconds"
+ , UI.option # set value "30" # set text "30 seconds"
+ , UI.option # set value "60" # set text "1 minute"
+ , UI.option # set value "300" # set text "5 minutes"
+ , UI.option # set value "900" # set text "15 minutes"
+ , UI.option # set value "1800" # set text "30 minutes"
+ , UI.option # set value "3600" # set text "1 hour"
+ ]
+
+ on UI.selectionChange selectTimeRange . const $
+ whenJustM (readMaybe <$> get value selectTimeRange) $ \(rangeInSec :: Int) -> do
+ Chart.setTimeRange chartId rangeInSec
+ when (rangeInSec == 0) $ Chart.resetZoomChartJS chartId
+ saveChartsSettings window
+
+ on UI.selectionChange selectUpdatePeriod . const $
+ whenJustM (readMaybe <$> get value selectUpdatePeriod) $ \(periodInSec :: Int) -> do
+ whenM (get UI.running chartUpdateTimer) $ UI.stop chartUpdateTimer
+ unless (periodInSec == 0) $ do
+ void $ return chartUpdateTimer # set UI.interval (periodInSec * 1000)
+ UI.start chartUpdateTimer
+ saveChartsSettings window
+
+ UI.div #. "rt-view-chart-container" #+
+ [ UI.div #. "columns" #+
+ [ UI.div #. "column mt-1" #+
+ [ UI.span #. "rt-view-chart-name" # set text chartName
+ ]
+ , UI.div #. "column has-text-right" #+
+ [ UI.div #. "field is-grouped mt-3" #+
+ [ image "has-tooltip-multiline has-tooltip-top rt-view-chart-icon" timeRangeSVG
+ # set dataTooltip "Select time range"
+ , UI.div #. "select is-link is-small mr-4" #+ [element selectTimeRange]
+ , image "has-tooltip-multiline has-tooltip-top rt-view-chart-icon" refreshSVG
+ # set dataTooltip "Select update period"
+ , UI.div #. "select is-link is-small" #+ [element selectUpdatePeriod]
+ ]
+ ]
+ ]
+ , UI.canvas ## show chartId #. "rt-view-chart-area" #+ []
+ ]
+
+shownState, hiddenState :: String
+shownState = "shown"
+hiddenState = "hidden"
+
+changeVisibilityForCharts
+ :: UI.Window
+ -> Element
+ -> Text
+ -> String
+ -> UI ()
+changeVisibilityForCharts window showHideIcon areaId areaName = do
+ state <- get dataState showHideIcon
+ let haveToHide = state == shownState
+ if haveToHide
+ then
+ findAndDo window areaId $ \el ->
+ fadeOut el 180 Swing $ runUI window $ do
+ void $ element el # hideIt
+ void $ element showHideIcon # set html hideSVG
+ # set dataState hiddenState
+ # set dataTooltip ("Click to show " <> areaName)
+ else
+ findAndDo window areaId $ \el -> do
+ void $ element el # showFlex
+ fadeIn el 180 Swing $ return ()
+ void $ element showHideIcon # set html showSVG
+ # set dataState shownState
+ # set dataTooltip ("Click to hide " <> areaName)
+
+mkChartTimer, mkChartTimer'
+ :: ConnectedNodes
+ -> History
+ -> DatasetsIndices
+ -> DatasetsTimestamps
+ -> DataName
+ -> ChartId
+ -> UI UI.Timer
+mkChartTimer = doMakeChartTimer addPointsToChart
+mkChartTimer' = doMakeChartTimer addAllPointsToChart
+
+type PointsAdder =
+ ConnectedNodes
+ -> History
+ -> DatasetsIndices
+ -> DatasetsTimestamps
+ -> DataName
+ -> ChartId
+ -> UI ()
+
+doMakeChartTimer
+ :: PointsAdder
+ -> ConnectedNodes
+ -> History
+ -> DatasetsIndices
+ -> DatasetsTimestamps
+ -> DataName
+ -> ChartId
+ -> UI UI.Timer
+doMakeChartTimer addPoints connectedNodes history datasetIndices
+ datasetTimestamps dataName chartId = do
+ uiUpdateTimer <- UI.timer # set UI.interval defaultUpdatePeriodInMs
+ on UI.tick uiUpdateTimer . const $
+ addPoints connectedNodes history datasetIndices datasetTimestamps dataName chartId
+ return uiUpdateTimer
+ where
+ defaultUpdatePeriodInMs = 15 * 1000
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Main.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Main.hs
new file mode 100644
index 00000000000..66b9edd857a
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Main.hs
@@ -0,0 +1,166 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Cardano.Tracer.Handlers.RTView.UI.HTML.Main
+ ( mkMainPage
+ ) where
+
+import Control.Concurrent.STM.TVar (readTVarIO)
+import Control.Monad (void)
+import Control.Monad.Extra (whenM)
+import Data.List.NonEmpty (NonEmpty)
+import qualified Graphics.UI.Threepenny as UI
+import Graphics.UI.Threepenny.Core
+import System.Time.Extra (sleep)
+
+import Cardano.Tracer.Configuration
+import Cardano.Tracer.Handlers.RTView.State.EraSettings
+import Cardano.Tracer.Handlers.RTView.State.Displayed
+import Cardano.Tracer.Handlers.RTView.State.Errors
+import Cardano.Tracer.Handlers.RTView.State.Historical
+import Cardano.Tracer.Handlers.RTView.State.Peers
+import Cardano.Tracer.Handlers.RTView.State.TraceObjects
+import Cardano.Tracer.Handlers.RTView.UI.CSS.Bulma
+import Cardano.Tracer.Handlers.RTView.UI.CSS.Own
+import Cardano.Tracer.Handlers.RTView.UI.HTML.Body
+import Cardano.Tracer.Handlers.RTView.UI.Img.Icons
+import Cardano.Tracer.Handlers.RTView.UI.Charts
+import Cardano.Tracer.Handlers.RTView.UI.Theme
+import Cardano.Tracer.Handlers.RTView.UI.Utils
+import Cardano.Tracer.Handlers.RTView.Update.EKG
+import Cardano.Tracer.Handlers.RTView.Update.Errors
+import Cardano.Tracer.Handlers.RTView.Update.KES
+import Cardano.Tracer.Handlers.RTView.Update.Nodes
+import Cardano.Tracer.Handlers.RTView.Update.NodeState
+import Cardano.Tracer.Handlers.RTView.Update.Peers
+import Cardano.Tracer.Handlers.RTView.Update.Reload
+import Cardano.Tracer.Types
+
+mkMainPage
+ :: ConnectedNodes
+ -> DisplayedElements
+ -> AcceptedMetrics
+ -> SavedTraceObjects
+ -> ErasSettings
+ -> DataPointRequestors
+ -> PageReloadedFlag
+ -> NonEmpty LoggingParams
+ -> Network
+ -> ResourcesHistory
+ -> BlockchainHistory
+ -> TransactionsHistory
+ -> Errors
+ -> UI.Window
+ -> UI ()
+mkMainPage connectedNodes displayedElements acceptedMetrics savedTO
+ nodesEraSettings dpRequestors reloadFlag loggingConfig networkConfig
+ resourcesHistory chainHistory txHistory nodesErrors window = do
+ void $ return window # set UI.title pageTitle
+ void $ UI.getHead window #+
+ [ UI.link # set UI.rel "icon"
+ # set UI.href ("data:image/svg+xml;base64," <> faviconSVGBase64)
+ , UI.meta # set UI.name "viewport"
+ # set UI.content "width=device-width, initial-scale=1"
+ -- CSS
+ , UI.mkElement "style" # set UI.html bulmaCSS
+ , UI.mkElement "style" # set UI.html bulmaTooltipCSS
+ , UI.mkElement "style" # set UI.html bulmaPageloaderCSS
+ , UI.mkElement "style" # set UI.html bulmaSwitchCSS
+ , UI.mkElement "style" # set UI.html bulmaDividerCSS
+ , UI.mkElement "style" # set UI.html ownCSS
+ ]
+
+ colors <- initColors
+ datasetIndices <- initDatasetsIndices
+ datasetTimestamps <- initDatasetsTimestamps
+ peers <- liftIO initPeers
+
+ pageBody <-
+ mkPageBody
+ window
+ networkConfig
+ connectedNodes
+ resourcesHistory
+ chainHistory
+ txHistory
+ datasetIndices
+ datasetTimestamps
+
+ -- Prepare and run the timer, which will hide the page preloader.
+ preloaderTimer <- UI.timer # set UI.interval 10
+ on UI.tick preloaderTimer . const $ do
+ liftIO $ sleep 0.8
+ findAndSet (set UI.class_ "pageloader") window "preloader"
+ UI.stop preloaderTimer
+ UI.start preloaderTimer
+
+ restoreTheme window
+ restoreChartsSettings
+
+ uiErrorsTimer <- UI.timer # set UI.interval 3000
+ on UI.tick uiErrorsTimer . const $
+ updateNodesErrors window connectedNodes nodesErrors
+
+ whenM (liftIO $ readTVarIO reloadFlag) $ do
+ updateUIAfterReload
+ window
+ connectedNodes
+ displayedElements
+ dpRequestors
+ loggingConfig
+ colors
+ datasetIndices
+ nodesErrors
+ uiErrorsTimer
+ liftIO $ pageWasNotReload reloadFlag
+
+ -- Uptime is a real-time clock, so update it every second.
+ uiUptimeTimer <- UI.timer # set UI.interval 1000
+ on UI.tick uiUptimeTimer . const $
+ updateNodesUptime connectedNodes displayedElements
+
+ uiEKGTimer <- UI.timer # set UI.interval 1000
+ on UI.tick uiEKGTimer . const $
+ updateEKGMetrics acceptedMetrics
+
+ uiNodesTimer <- UI.timer # set UI.interval 1500
+ on UI.tick uiNodesTimer . const $
+ updateNodesUI
+ window
+ connectedNodes
+ displayedElements
+ acceptedMetrics
+ savedTO
+ nodesEraSettings
+ dpRequestors
+ loggingConfig
+ colors
+ datasetIndices
+ nodesErrors
+ uiErrorsTimer
+
+ uiPeersTimer <- UI.timer # set UI.interval 3000
+ on UI.tick uiPeersTimer . const $ do
+ updateNodesPeers window peers savedTO
+ updateKESInfo window acceptedMetrics nodesEraSettings displayedElements
+
+ uiNodeStateTimer <- UI.timer # set UI.interval 5000
+ on UI.tick uiNodeStateTimer . const $
+ askNSetNodeState window connectedNodes dpRequestors displayedElements
+
+ UI.start uiUptimeTimer
+ UI.start uiNodesTimer
+ UI.start uiNodeStateTimer
+ UI.start uiPeersTimer
+ UI.start uiErrorsTimer
+ UI.start uiEKGTimer
+
+ on UI.disconnect window . const $ do
+ UI.stop uiNodesTimer
+ UI.stop uiUptimeTimer
+ UI.stop uiPeersTimer
+ UI.stop uiNodeStateTimer
+ UI.stop uiEKGTimer
+ UI.stop uiErrorsTimer
+ liftIO $ pageWasReload reloadFlag
+
+ void $ UI.element pageBody
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/NoNodes.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/NoNodes.hs
new file mode 100644
index 00000000000..ab3ebd20b56
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/NoNodes.hs
@@ -0,0 +1,136 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE QuasiQuotes #-}
+
+module Cardano.Tracer.Handlers.RTView.UI.HTML.NoNodes
+ ( mkNoNodesInfo
+ ) where
+
+import Data.List (intercalate)
+import qualified Data.List.NonEmpty as NE
+import Data.String.QQ
+import qualified Graphics.UI.Threepenny as UI
+import Graphics.UI.Threepenny.Core
+
+import Cardano.Tracer.Configuration
+import Cardano.Tracer.Handlers.RTView.UI.Img.Icons
+import Cardano.Tracer.Handlers.RTView.UI.Utils
+
+-- | If the user doesn't see connected nodes - possible reason of it is
+-- misconfiguration of 'cardano-tracer' and/or 'cardano-node'.
+-- So we have to show basic explanation, which is based on current
+-- configuration of 'cardano-tracer'.
+mkNoNodesInfo :: Network -> UI Element
+mkNoNodesInfo networkConfig = do
+ closeIt <- UI.button #. "delete" # set (UI.attr "aria-label") "delete"
+ infoNote <-
+ UI.mkElement "article" ## "no-nodes-info"
+ #. "container message is-link rt-view-no-nodes-info" #+
+ [ UI.div #. "message-header" #+
+ [ UI.p # set text "«Hey, where are my nodes?»"
+ , element closeIt
+ ]
+ , UI.div #. "message-body" #+
+ [ UI.p #+
+ [ UI.span # set UI.html pleaseWait
+ ]
+ , UI.p #. "mt-5" #+
+ [ string intro
+ ]
+ , UI.p #. "mt-5" #+
+ [ UI.span # set UI.html cardanoTracerNote
+ ]
+ , UI.p #. "mt-5" #+
+ [ UI.span # set UI.html cardanoNodeNote
+ ]
+ , UI.p #. "mt-5" #+
+ [ UI.span # set UI.html nodeNameNote
+ ]
+ , UI.p #. "mt-5" #+
+ [ UI.span # set UI.html sshNote
+ ]
+ , UI.p #. "mt-5" #+
+ [ string "For more details, please read "
+ , UI.anchor # set UI.href "https://github.com/input-output-hk/cardano-node/blob/master/cardano-tracer/docs/cardano-tracer.md#configuration"
+ # set text "our documentation"
+ # set UI.target "_blank"
+ , image "rt-view-href-icon" externalLinkSVG
+ , string "."
+ ]
+ ]
+ ]
+ on UI.click closeIt . const $ element infoNote # hideIt
+ return infoNote
+ where
+ pleaseWait =
+ "If your nodes and cardano-tracer
are configured properly, "
+ <> "the connection between them will be established automatically, "
+ <> "but it can take some time."
+
+ intro =
+ "However, if there is no connection after 1 minute, please check your configuration files."
+
+ cardanoTracerNote =
+ case networkConfig of
+ AcceptAt (LocalSocket p) ->
+ "Currently, your cardano-tracer
is configured as a server, "
+ <> "so it accepts connections from your nodes via the local socket "
+ <> p <> "
."
+ ConnectTo addrs ->
+ let manySocks = NE.length addrs > 1 in
+ "Currently, your cardano-tracer
is configured as a client, "
+ <> "so it connects to your "
+ <> (if manySocks then "nodes" else "node")
+ <> " via the local "
+ <> (if manySocks
+ then
+ let socks = map (\(LocalSocket p) -> "" <> p <> "
") $ NE.toList addrs
+ in "sockets " <> intercalate ", " socks <> "."
+ else
+ "socket " <> let LocalSocket p = NE.head addrs in p <> "
.")
+
+ cardanoNodeNote =
+ case networkConfig of
+ AcceptAt (LocalSocket _p) ->
+ "Correspondingly, your nodes should be configured as clients. Make sure their configuration "
+ <> "files contain TraceOptionForwarder
section like this:"
+ <> "
" <> traceOptionForwarderInit <> "" + <> "where
PATH_TO_SOCK
is a path to a local socket."
+ ConnectTo _addrs ->
+ "Correspondingly, your nodes should be configured as servers. Make sure their configuration "
+ <> "files contain TraceOptionForwarder
section like this:"
+ <> "" <> traceOptionForwarderResp <> "" + <> "where
PATH_TO_SOCK
is a path to a local socket."
+
+ nodeNameNote =
+ "Also, please add a meaningful name for your nodes using TraceOptionNodeName
field. "
+ <> "For example: " <> traceOptionNodeName <> "" + + sshNote = + "If your
cardano-tracer
and your nodes are running on different machines, the only "
+ <> "way to connect them is SSH tunneling with your credentials."
+
+traceOptionForwarderInit :: String
+traceOptionForwarderInit = [s|
+"TraceOptionForwarder": {
+ "address": {
+ "filePath": "PATH_TO_SOCK"
+ },
+ "mode": "Initiator"
+}
+|]
+
+traceOptionForwarderResp :: String
+traceOptionForwarderResp = [s|
+"TraceOptionForwarder": {
+ "address": {
+ "filePath": "PATH_TO_SOCK"
+ },
+ "mode": "Responder"
+}
+|]
+
+traceOptionNodeName :: String
+traceOptionNodeName = [s|
+"TraceOptionNodeName": "stk-a-1-IOG1"
+|]
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/Column.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/Column.hs
new file mode 100644
index 00000000000..b7bba8dcf44
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/Column.hs
@@ -0,0 +1,243 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Cardano.Tracer.Handlers.RTView.UI.HTML.Node.Column
+ ( addNodeColumn
+ , deleteNodeColumn
+ ) where
+
+import Control.Monad (forM, void)
+import Control.Monad.Extra (whenJustM)
+import Data.List.NonEmpty (NonEmpty)
+import qualified Data.List.NonEmpty as NE
+import Data.Text (unpack)
+import qualified Graphics.UI.Threepenny as UI
+import Graphics.UI.Threepenny.Core
+import System.FilePath ((>))
+
+import Cardano.Tracer.Configuration
+import Cardano.Tracer.Handlers.RTView.State.Errors
+import Cardano.Tracer.Handlers.RTView.UI.HTML.Node.EKG
+import Cardano.Tracer.Handlers.RTView.UI.HTML.Node.Errors
+import Cardano.Tracer.Handlers.RTView.UI.HTML.Node.Peers
+import Cardano.Tracer.Handlers.RTView.UI.JS.Utils
+import Cardano.Tracer.Handlers.RTView.UI.Img.Icons
+import Cardano.Tracer.Handlers.RTView.UI.Utils
+import Cardano.Tracer.Types
+
+-- | For every connected node the new column should be added.
+addNodeColumn
+ :: UI.Window
+ -> NonEmpty LoggingParams
+ -> Errors
+ -> UI.Timer
+ -> NodeId
+ -> UI ()
+addNodeColumn window loggingConfig nodesErrors updateErrorsTimer nodeId@(NodeId anId) = do
+ let id' = unpack anId
+ ls <- logsSettings loggingConfig id'
+
+ peersTable <- mkPeersTable id'
+ peersDetailsButton <- UI.button ## (id' <> "__node-peers-details-button")
+ #. "button is-info"
+ # set UI.enabled False
+ # set text "Details"
+ on UI.click peersDetailsButton . const $ fadeInModal peersTable
+
+ errorsTable <- mkErrorsTable window nodeId nodesErrors updateErrorsTimer
+ errorsDetailsButton <- UI.button ## (id' <> "__node-errors-details-button")
+ #. "button is-danger"
+ # set UI.enabled False
+ # set text "Details"
+ on UI.click errorsDetailsButton . const $ fadeInModal errorsTable
+
+ ekgMetricsWindow <- mkEKGMetricsWindow id'
+ ekgMetricsButton <- UI.button ## (id' <> "__node-ekg-metrics-button")
+ #. "button is-info"
+ # set text "Details"
+ on UI.click ekgMetricsButton . const $ fadeInModal ekgMetricsWindow
+
+ addNodeCellH "name" [ image "rt-view-node-chart-label has-tooltip-multiline has-tooltip-left" rectangleSVG
+ ## (id' <> "__node-chart-label")
+ # set dataTooltip "Label using for this node on charts"
+ , UI.span ## (id' <> "__node-name")
+ #. "has-text-weight-bold is-size-4 rt-view-node-name"
+ # set text "Node"
+ ]
+ addNodeCell "version" [ UI.span ## (id' <> "__node-version")
+ # set text "—"
+ ]
+ addNodeCell "commit" [ UI.anchor ## (id' <> "__node-commit")
+ #. ("rt-view-href is-family-monospace has-text-weight-normal"
+ <> " has-tooltip-multiline has-tooltip-right")
+ # set UI.href "#"
+ # set UI.target "_blank"
+ # set dataTooltip "Browse cardano-node repository on this commit"
+ # set text "—"
+ , image "rt-view-href-icon" externalLinkSVG
+ ]
+ addNodeCell "protocol" [ UI.span ## (id' <> "__node-protocol")
+ # set text "—"
+ ]
+ addNodeCell "era" [ UI.span ## (id' <> "__node-era")
+ # set text "—"
+ ]
+ addNodeCell "epoch" [ string "#"
+ , UI.span ## (id' <> "__node-epoch-num") # set text "—"
+ , image "has-tooltip-multiline has-tooltip-top rt-view-epoch-end" endSVG
+ # set dataTooltip "End date of this epoch"
+ , UI.span ## (id' <> "__node-epoch-end") # set text "—"
+ ]
+ addNodeCell "sync" [ UI.span ## (id' <> "__node-sync-progress")
+ # set text "—"
+ ]
+ addNodeCell "system-start-time" [ UI.span ## (id' <> "__node-system-start-time")
+ # set text "—"
+ ]
+ addNodeCell "start-time" [ UI.span ## (id' <> "__node-start-time")
+ # set text "—"
+ ]
+ addNodeCell "uptime" [ UI.span ## (id' <> "__node-uptime")
+ # set text "—"
+ ]
+ addNodeCell "logs" [ UI.span ## (id' <> "__node-logs")
+ #+ ls
+ ]
+ addNodeCell "block-replay" [ UI.span ## (id' <> "__node-block-replay")
+ # set html "0 %"
+ ]
+ addNodeCell "chunk-validation" [ UI.span ## (id' <> "__node-chunk-validation")
+ # set text "—"
+ ]
+ addNodeCell "update-ledger-db" [ UI.span ## (id' <> "__node-update-ledger-db")
+ # set html "0 %"
+ ]
+ addNodeCell "peers" [ UI.div #. "buttons has-addons" #+
+ [ UI.button ## (id' <> "__node-peers-num")
+ #. "button is-static"
+ # set text "—"
+ , element peersDetailsButton
+ ]
+ , element peersTable
+ ]
+ addNodeCell "errors" [ UI.div #. "buttons has-addons" #+
+ [ UI.button ## (id' <> "__node-errors-num")
+ #. "button is-static"
+ # set text "0"
+ , element errorsDetailsButton
+ ]
+ , element errorsTable
+ ]
+ addNodeCell "leadership" [ UI.span ## (id' <> "__node-leadership")
+ # set text "—"
+ ]
+ addNodeCell "forged-blocks" [ UI.span ## (id' <> "__node-forged-blocks")
+ # set text "—"
+ ]
+ addNodeCell "cannot-forge" [ UI.span ## (id' <> "__node-cannot-forge")
+ # set text "—"
+ ]
+ addNodeCell "missed-slots" [ UI.span ## (id' <> "__node-missed-slots")
+ # set text "—"
+ ]
+ addNodeCell "current-kes-period" [ UI.span ## (id' <> "__node-current-kes-period")
+ # set text "—"
+ ]
+ addNodeCell "op-cert-expiry-kes-period" [ UI.span ## (id' <> "__node-op-cert-expiry-kes-period")
+ # set text "—"
+ ]
+ addNodeCell "remaining-kes-periods" [ UI.span ## (id' <> "__node-remaining-kes-periods")
+ # set text "—"
+ ]
+ addNodeCell "op-cert-start-kes-period" [ UI.span ## (id' <> "__node-op-cert-start-kes-period")
+ # set text "—"
+ ]
+ addNodeCell "days-until-op-cert-renew" [ UI.span ## (id' <> "__node-days-until-op-cert-renew")
+ # set text "—"
+ ]
+ addNodeCell "ekg-metrics" [ UI.div #. "buttons has-addons" #+
+ [ UI.button ## (id' <> "__node-ekg-metrics-num")
+ #. "button is-static"
+ # set text "—"
+ , element ekgMetricsButton
+ ]
+ , element ekgMetricsWindow
+ ]
+ where
+ addNodeCellH rowId cellContent =
+ whenJustM (UI.getElementById window ("node-" <> rowId <> "-row")) $ \el ->
+ void $ element el #+ [ UI.th #. (unpack anId <> "__column_cell")
+ #+ cellContent
+ ]
+ addNodeCell rowId cellContent =
+ whenJustM (UI.getElementById window ("node-" <> rowId <> "-row")) $ \el ->
+ void $ element el #+ [ UI.td #. (unpack anId <> "__column_cell")
+ #+ cellContent
+ ]
+
+-- | The new node is already connected, so we can display its logging settings.
+logsSettings
+ :: NonEmpty LoggingParams
+ -> String
+ -> UI [UI Element]
+logsSettings loggingConfig anId =
+ forM (NE.toList loggingConfig) $ \(LoggingParams root mode format) ->
+ case mode of
+ FileMode -> do
+ let pathToSubdir = root > anId
+
+ copyPath <- UI.button #. "button is-info"
+ #+ [image "rt-view-copy-icon" copySVG]
+ on UI.click copyPath . const $
+ copyTextToClipboard pathToSubdir
+
+ return $
+ UI.p #+
+ [ UI.div #. "field has-addons" #+
+ [ UI.p #. "control" #+
+ [ UI.button #. "button is-static"
+ # set text (if format == ForHuman then "LOG" else "JSON")
+ ]
+ , UI.p #. "control" #+
+ [ UI.input #. "input rt-view-logs-input"
+ # set UI.type_ "text"
+ # set (UI.attr "readonly") "readonly"
+ # set UI.value pathToSubdir
+ ]
+ , UI.p #. "control" #+
+ [ element copyPath
+ ]
+ ]
+ ]
+ JournalMode -> do
+ copyId <- UI.button #. "button is-info"
+ #+ [image "rt-view-copy-icon" copySVG]
+ on UI.click copyId . const $
+ copyTextToClipboard anId
+
+ return $
+ UI.p #+
+ [ UI.div #. "field has-addons" #+
+ [ UI.p #. "control" #+
+ [ UI.button #. "button is-static"
+ # set text "JRNL"
+ ]
+ , UI.p #. "control" #+
+ [ UI.input #. "input rt-view-logs-input"
+ # set UI.type_ "text"
+ # set (UI.attr "readonly") "readonly"
+ # set UI.value anId
+ ]
+ , UI.p #. "control" #+
+ [ element copyId
+ ]
+ ]
+ ]
+
+-- | The node was disconnected, so its column should be deleted.
+deleteNodeColumn
+ :: UI.Window
+ -> NodeId
+ -> UI ()
+deleteNodeColumn window (NodeId anId) = do
+ let className = anId <> "__column_cell"
+ findByClassAndDo window className delete'
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/EKG.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/EKG.hs
new file mode 100644
index 00000000000..dbf44952ffe
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/EKG.hs
@@ -0,0 +1,48 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Cardano.Tracer.Handlers.RTView.UI.HTML.Node.EKG
+ ( mkEKGMetricsWindow
+ ) where
+
+import qualified Graphics.UI.Threepenny as UI
+import Graphics.UI.Threepenny.Core
+
+import Cardano.Tracer.Handlers.RTView.UI.Utils
+
+mkEKGMetricsWindow :: String -> UI Element
+mkEKGMetricsWindow anId = do
+ closeIt <- UI.button #. "delete"
+ metricsWindow <-
+ UI.div #. "modal" #+
+ [ UI.div #. "modal-background" #+ []
+ , UI.div #. "modal-card rt-view-ekg-metrics-modal" #+
+ [ UI.header #. "modal-card-head rt-view-ekg-metrics-head" #+
+ [ UI.p #. "modal-card-title rt-view-ekg-metrics-title" #+
+ [ string "EKG metrics from "
+ , UI.span ## (anId <> "__node-name-for-ekg-metrics")
+ #. "has-text-weight-bold"
+ # set text anId
+ ]
+ , element closeIt
+ ]
+ , UI.mkElement "section" #. "modal-card-body rt-view-ekg-metrics-body" #+
+ [ UI.div ## "ekg-metrics-columns" #. "columns" #+
+ [ UI.div #. "column" #+
+ [ UI.span #. "is-size-4 has-text-weight-bold" # set text "Metric name"
+ , UI.p #. "mt-3" #+
+ [ UI.span ## (anId <> "__node-ekg-metrics-names") #. "is-family-monospace" # set text "—"
+ ]
+ ]
+ , UI.div #. "column has-text-right" #+
+ [ UI.span #. "is-size-4 has-text-weight-bold" # set text "Metric value"
+ , UI.p #. "mt-3" #+
+ [ UI.span ## (anId <> "__node-ekg-metrics-values") # set text "—"
+ ]
+ ]
+ ]
+ ]
+ ]
+ ]
+ on UI.click closeIt . const $ element metricsWindow #. "modal"
+ return metricsWindow
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/Errors.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/Errors.hs
new file mode 100644
index 00000000000..9384d17bcf3
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/Errors.hs
@@ -0,0 +1,121 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Cardano.Tracer.Handlers.RTView.UI.HTML.Node.Errors
+ ( mkErrorsTable
+ ) where
+
+import Control.Monad (when)
+import Control.Monad.Extra (unlessM)
+import Data.Text (unpack)
+import qualified Graphics.UI.Threepenny as UI
+import Graphics.UI.Threepenny.Core
+
+import Cardano.Tracer.Handlers.RTView.State.Errors
+import Cardano.Tracer.Handlers.RTView.UI.Img.Icons
+import Cardano.Tracer.Handlers.RTView.UI.Utils
+import Cardano.Tracer.Handlers.RTView.Update.Errors
+import Cardano.Tracer.Types
+
+mkErrorsTable
+ :: UI.Window
+ -> NodeId
+ -> Errors
+ -> UI.Timer
+ -> UI Element
+mkErrorsTable window nodeId@(NodeId anId) nodesErrors updateErrorsTimer = do
+ let id' = unpack anId
+ closeIt <- UI.button #. "delete"
+ deleteAll <- image "has-tooltip-multiline has-tooltip-left rt-view-delete-errors-icon" trashSVG
+ # set dataTooltip "Click to delete all errors. This action cannot be undone!"
+ on UI.click deleteAll . const $
+ deleteAllErrorMessages window nodeId nodesErrors
+
+ searchMessagesInput <- UI.input #. "input rt-view-search-messages"
+ # set UI.type_ "text"
+ # set (UI.attr "placeholder") "Search messages"
+ searchMessages <- UI.button #. "button is-info"
+ #+ [image "rt-view-search-errors-icon" searchSVG]
+
+ -- If the user clicked the search button.
+ on UI.click searchMessages . const $
+ searchErrorMessages window searchMessagesInput nodeId nodesErrors updateErrorsTimer
+ -- If the user hits Enter key.
+ on UI.keyup searchMessagesInput $ \keyCode ->
+ when (keyCode == 13) $
+ searchErrorMessages window searchMessagesInput nodeId nodesErrors updateErrorsTimer
+ -- If the user changed text in input...
+ on UI.valueChange searchMessagesInput $ \inputText ->
+ when (null inputText) $
+ -- ... and this text is empty, it means that search/filter mode is off,
+ -- so remove "search result" and start the timer for update errors again.
+ unlessM (get UI.running updateErrorsTimer) $
+ -- Ok, the timer is stopped, so we are in search/filter mode already, exit it.
+ exitSearchMode window nodeId updateErrorsTimer
+
+ sortByTimeIcon <- image "has-tooltip-multiline has-tooltip-right rt-view-sort-icon" sortSVG
+ # set dataTooltip "Click to sort errors by time"
+ # set dataState "asc"
+ on UI.click sortByTimeIcon . const $
+ sortErrorsByTime window nodeId sortByTimeIcon nodesErrors
+
+ sortBySeverityIcon <- image "has-tooltip-multiline has-tooltip-right rt-view-sort-icon" sortSVG
+ # set dataTooltip "Click to sort errors by severity"
+ # set dataState "asc"
+ on UI.click sortBySeverityIcon . const $
+ sortErrorsBySeverity window nodeId sortBySeverityIcon nodesErrors
+
+ errorsTable <-
+ UI.div #. "modal" #+
+ [ UI.div #. "modal-background" #+ []
+ , UI.div #. "modal-card rt-view-errors-modal" #+
+ [ UI.header #. "modal-card-head rt-view-errors-head" #+
+ [ UI.p #. "modal-card-title rt-view-errors-title" #+
+ [ string "Errors from "
+ , UI.span ## (id' <> "__node-name-for-errors")
+ #. "has-text-weight-bold"
+ # set text id'
+ ]
+ , element closeIt
+ ]
+ , UI.mkElement "section" #. "modal-card-body rt-view-errors-body" #+
+ [ UI.div ## (id' <> "__errors-table-container") #. "table-container" #+
+ [ UI.table ## (id' <> "__errors-table") #. "table is-fullwidth rt-view-errors-table" #+
+ [ UI.mkElement "thead" #+
+ [ UI.tr #+
+ [ UI.th #. "rt-view-errors-timestamp" #+
+ [ string "Timestamp"
+ , element sortByTimeIcon
+ ]
+ , UI.th #. "rt-view-errors-severity" #+
+ [ string "Severity"
+ , element sortBySeverityIcon
+ ]
+ , UI.th #+
+ [ string "Message"
+ ]
+ , UI.th #+
+ [ element deleteAll
+ ]
+ ]
+ ]
+ , UI.mkElement "tbody" ## (id' <> "__node-errors-tbody")
+ # set dataState "0"
+ #+ []
+ ]
+ ]
+ ]
+ , UI.mkElement "footer" #. "modal-card-foot rt-view-errors-foot" #+
+ [ UI.div #. "field has-addons" #+
+ [ UI.p #. "control" #+
+ [ element searchMessagesInput
+ ]
+ , UI.p #. "control" #+
+ [ element searchMessages
+ ]
+ ]
+ ]
+ ]
+ ]
+ on UI.click closeIt . const $ element errorsTable #. "modal"
+ return errorsTable
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/Peers.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/Peers.hs
new file mode 100644
index 00000000000..cde6de695b9
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/Peers.hs
@@ -0,0 +1,62 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Cardano.Tracer.Handlers.RTView.UI.HTML.Node.Peers
+ ( mkPeersTable
+ , deletePeerRow
+ ) where
+
+import qualified Graphics.UI.Threepenny as UI
+import Graphics.UI.Threepenny.Core
+
+import Cardano.Tracer.Handlers.RTView.State.Peers
+import Cardano.Tracer.Handlers.RTView.UI.Utils
+import Cardano.Tracer.Types
+
+mkPeersTable :: String -> UI Element
+mkPeersTable anId = do
+ closeIt <- UI.button #. "delete"
+ peerTable <-
+ UI.div #. "modal" #+
+ [ UI.div #. "modal-background" #+ []
+ , UI.div #. "modal-card rt-view-peer-modal" #+
+ [ UI.header #. "modal-card-head rt-view-peer-head" #+
+ [ UI.p #. "modal-card-title rt-view-peer-title" #+
+ [ string "Peers of "
+ , UI.span ## (anId <> "__node-name-for-peers")
+ #. "has-text-weight-bold"
+ # set text anId
+ ]
+ , element closeIt
+ ]
+ , UI.mkElement "section" #. "modal-card-body rt-view-peer-body" #+
+ [ UI.div ## (anId <> "__peer-table-container") #. "table-container" #+
+ [ UI.table ## (anId <> "__peer-table") #. "table is-fullwidth rt-view-peer-table" #+
+ [ UI.mkElement "thead" #+
+ [ UI.tr #+
+ [ UI.th #+ [UI.span # set text "Address"]
+ , UI.th #+ [UI.span # set text "Status"]
+ , UI.th #+ [UI.span # set text "Slot no."]
+ , UI.th #+ [UI.mkElement "abbr" # set UI.title__ "Requests in flight" # set text "Req"]
+ , UI.th #+ [UI.mkElement "abbr" # set UI.title__ "Blocks in flight" # set text "Blk"]
+ , UI.th #+ [UI.mkElement "abbr" # set UI.title__ "Bytes in flight" # set text "Bts"]
+ ]
+ ]
+ , UI.mkElement "tbody" ## (anId <> "__node-peers-tbody") #+ []
+ ]
+ ]
+ ]
+ ]
+ ]
+ on UI.click closeIt . const $ element peerTable #. "modal"
+ return peerTable
+
+-- | The peer was disconnected, so its row should be deleted.
+deletePeerRow
+ :: UI.Window
+ -> NodeId
+ -> PeerAddress
+ -> UI ()
+deletePeerRow window (NodeId anId) peerAddr = do
+ let peerRowElId = anId <> peerAddr <> "__node-peer-row"
+ findAndDo window peerRowElId delete'
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Notifications.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Notifications.hs
new file mode 100644
index 00000000000..664e86787ab
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Notifications.hs
@@ -0,0 +1,62 @@
+module Cardano.Tracer.Handlers.RTView.UI.HTML.Notifications
+ ( mkNotificationsEvents
+ , mkNotificationsSettings
+ ) where
+
+--import qualified Data.Text as T
+import qualified Graphics.UI.Threepenny as UI
+import Graphics.UI.Threepenny.Core
+
+--import Cardano.Tracer.Handlers.RTView.UI.JS.Utils
+--import Cardano.Tracer.Handlers.RTView.UI.Img.Icons
+import Cardano.Tracer.Handlers.RTView.UI.Utils
+
+mkNotificationsEvents :: UI Element
+mkNotificationsEvents = do
+ closeIt <- UI.button #. "delete"
+ notifications <-
+ UI.div #. "modal" #+
+ [ UI.div #. "modal-background" #+ []
+ , UI.div #. "modal-card" #+
+ [ UI.header #. "modal-card-head rt-view-notifications-head" #+
+ [ UI.p #. "modal-card-title rt-view-notifications-title" # set text "Notifications: events"
+ , element closeIt
+ ]
+ , UI.mkElement "section" #. "modal-card-body rt-view-notifications-body" #+
+ [ UI.div #. "field" #+
+ [ UI.input ## "switchRoundedInfo"
+ #. "switch is-rounded is-info"
+ # set UI.type_ "checkbox"
+ # set UI.name "switchRoundedInfo"
+ , UI.label # set UI.for "switchRoundedInfo"
+ # set text "Switch info"
+ ]
+ ]
+ ]
+ ]
+ on UI.click closeIt . const $ element notifications #. "modal"
+ return notifications
+
+mkNotificationsSettings :: UI Element
+mkNotificationsSettings = do
+ closeIt <- UI.button #. "delete"
+ notifications <-
+ UI.div #. "modal" #+
+ [ UI.div #. "modal-background" #+ []
+ , UI.div #. "modal-card" #+
+ [ UI.header #. "modal-card-head rt-view-notifications-head" #+
+ [ UI.p #. "modal-card-title rt-view-notifications-title" # set text "Notifications: settings"
+ , element closeIt
+ ]
+ , UI.mkElement "section" #. "modal-card-body rt-view-notifications-body" #+
+ [ string "SETTINGS"
+ ]
+ ]
+ ]
+ on UI.click closeIt . const $ element notifications #. "modal"
+ return notifications
+
+
+
+
+
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/Img/Icons.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/Img/Icons.hs
new file mode 100644
index 00000000000..4f626f8b81e
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/Img/Icons.hs
@@ -0,0 +1,354 @@
+{-# LANGUAGE QuasiQuotes #-}
+
+module Cardano.Tracer.Handlers.RTView.UI.Img.Icons
+ ( blocksSVG
+ , calendarSVG
+ , cardanoLogoSVG
+ , chainSVG
+ , commitSVG
+ , connectedSVG
+ , dbSVG
+ , downSVG
+ , errorsSVG
+ , faviconSVGBase64
+ , healthSVG
+ , kesSVG
+ , linuxSVG
+ , noNodesSVG
+ , overviewSVG
+ , peersSVG
+ , peersNumSVG
+ , platformSVG
+ , configSVG
+ , protocolSVG
+ , remainingSVG
+ , rtViewInfoSVG
+ , rtViewNotifySVG
+ , rtViewThemeToLightSVG
+ , rtViewThemeToDarkSVG
+ , serverSVG
+ , whatSVG
+ , versionSVG
+ , startSVG
+ , systemStartSVG
+ , uptimeSVG
+ , logsSVG
+ , directorySVG
+ , copySVG
+ , externalLinkSVG
+ , externalLinkWhiteSVG
+ , rectangleSVG
+ , showSVG
+ , hideSVG
+ , githubSVG
+ , refreshSVG
+ , timeRangeSVG
+ , leaderSVG
+ , forgeSVG
+ , notForgeSVG
+ , missedSVG
+ , eraSVG
+ , lengthSVG
+ , endSVG
+ , ekgMetricsSVG
+ , certificateSVG
+ , csvSVG
+ , deleteSVG
+ , trashSVG
+ , sortSVG
+ , searchSVG
+ , eventsSVG
+ , settingsSVG
+ ) where
+
+import Data.String.QQ
+
+cardanoLogoSVG :: String
+cardanoLogoSVG = [s|
+
+|]
+
+rtViewInfoSVG :: String
+rtViewInfoSVG = [s|
+
+|]
+
+rtViewNotifySVG :: String
+rtViewNotifySVG = [s|
+
+|]
+
+rtViewThemeToLightSVG :: String
+rtViewThemeToLightSVG = [s|
+
+|]
+
+rtViewThemeToDarkSVG :: String
+rtViewThemeToDarkSVG = [s|
+
+|]
+
+whatSVG :: String
+whatSVG = [s|
+
+|]
+
+dbSVG :: String
+dbSVG = [s|
+
+|]
+
+downSVG :: String
+downSVG = [s|
+
+|]
+
+protocolSVG :: String
+protocolSVG = [s|
+
+|]
+
+versionSVG :: String
+versionSVG = [s|
+
+|]
+
+commitSVG :: String
+commitSVG = [s|
+
+|]
+
+linuxSVG :: String
+linuxSVG = [s|
+
+|]
+
+calendarSVG :: String
+calendarSVG = [s|
+
+|]
+
+healthSVG :: String
+healthSVG = [s|
+
+|]
+
+overviewSVG :: String
+overviewSVG = [s|
+
+|]
+
+kesSVG :: String
+kesSVG = [s|
+
+|]
+
+peersSVG :: String
+peersSVG = [s|
+
+|]
+
+blocksSVG :: String
+blocksSVG = [s|
+
+|]
+
+errorsSVG :: String
+errorsSVG = [s|
+
+|]
+
+noNodesSVG :: String
+noNodesSVG = [s|
+
+|]
+
+connectedSVG :: String
+connectedSVG = [s|
+
+|]
+
+remainingSVG :: String
+remainingSVG = [s|
+
+|]
+
+peersNumSVG :: String
+peersNumSVG = [s|
+
+|]
+
+chainSVG :: String
+chainSVG = [s|
+
+|]
+
+faviconSVGBase64 :: String
+faviconSVGBase64 = [s|
+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzNzUgMzQ2LjUxIj48ZyBpZD0iTGF5ZXJfMiIgZGF0YS1uYW1lPSJMYXllciAyIj48ZyBpZD0iTGF5ZXJfMS0yIiBkYXRhLW5hbWU9IkxheWVyIDEiPjxwYXRoIGQ9Ik0xMDIuNzYsMTcyYTI1LjMxLDI1LjMxLDAsMCwwLDIzLjc4LDI2LjY1Yy40OSwwLDEsMCwxLjQ2LDBBMjUuMjYsMjUuMjYsMCwxLDAsMTAyLjc2LDE3MloiIGZpbGw9IiMwMDMzYWQiLz48cGF0aCBkPSJNOC42MiwxNjUuNWE4LjE2LDguMTYsMCwxLDAsNy42OSw4LjYxQTguMTUsOC4xNSwwLDAsMCw4LjYyLDE2NS41WiIgZmlsbD0iIzAwMzNhZCIvPjxwYXRoIGQ9Ik0xMDEuMTYsMjUuNDNhOC4xNiw4LjE2LDAsMSwwLTExLTMuNjJBOC4xOCw4LjE4LDAsMCwwLDEwMS4xNiwyNS40M1oiIGZpbGw9IiMwMDMzYWQiLz48cGF0aCBkPSJNMTI2Ljc4LDcwLjFhMTIuNjEsMTIuNjEsMCwxLDAtMTYuOTQtNS41OUExMi42MiwxMi42MiwwLDAsMCwxMjYuNzgsNzAuMVoiIGZpbGw9IiMwMDMzYWQiLz48cGF0aCBkPSJNNDAuNTgsMTAwLjgyYTEwLjM5LDEwLjM5LDAsMSwwLTMtMTQuMzhBMTAuMzksMTAuMzksMCwwLDAsNDAuNTgsMTAwLjgyWiIgZmlsbD0iIzAwMzNhZCIvPjxwYXRoIGQ9Ik01NS45MywxNjFhMTIuNjIsMTIuNjIsMCwxLDAsMTEuODgsMTMuMzFBMTIuNjIsMTIuNjIsMCwwLDAsNTUuOTMsMTYxWiIgZmlsbD0iIzAwMzNhZCIvPjxwYXRoIGQ9Ik00MiwyNDUuNzJhMTAuMzksMTAuMzksMCwxLDAsMTMuOTUsNC42QTEwLjM3LDEwLjM3LDAsMCwwLDQyLDI0NS43MloiIGZpbGw9IiMwMDMzYWQiLz48cGF0aCBkPSJNOTEsMTM0Ljg5YTE0Ljg0LDE0Ljg0LDAsMSwwLTQuMjctMjAuNTVBMTQuODMsMTQuODMsMCwwLDAsOTEsMTM0Ljg5WiIgZmlsbD0iIzAwMzNhZCIvPjxwYXRoIGQ9Ik0yNDYuNDcsNjkuMWExMi42MiwxMi42MiwwLDEsMC0zLjYzLTE3LjQ3QTEyLjYxLDEyLjYxLDAsMCwwLDI0Ni40Nyw2OS4xWiIgZmlsbD0iIzAwMzNhZCIvPjxwYXRoIGQ9Ik0yNzIuMzUsMjQuNTdBOC4xNiw4LjE2LDAsMSwwLDI3MCwxMy4yNiw4LjE2LDguMTYsMCwwLDAsMjcyLjM1LDI0LjU3WiIgZmlsbD0iIzAwMzNhZCIvPjxwYXRoIGQ9Ik0yNDguNDUsMTQ3LjkxYTI1LjI1LDI1LjI1LDAsMCwwLTIuODcsNTAuNDJjLjQ5LDAsMSwwLDEuNDUsMGEyNS4yNSwyNS4yNSwwLDAsMCwxLjQyLTUwLjQ2WiIgZmlsbD0iIzAwMzNhZCIvPjxwYXRoIGQ9Ik0xMzUuMDgsMTMzLjE0QTI1LjEyLDI1LjEyLDAsMCwwLDE1Ny42NCwxNDdhMjUuMjUsMjUuMjUsMCwwLDAsMjIuNTQtMzYuNjIsMjUuMjUsMjUuMjUsMCwxLDAtNDUuMSwyMi43M1oiIGZpbGw9IiMwMDMzYWQiLz48cGF0aCBkPSJNMzMzLDEwMC43OWExMC4zOSwxMC4zOSwwLDEsMC0xNC00LjZBMTAuNCwxMC40LDAsMCwwLDMzMywxMDAuNzlaIiBmaWxsPSIjMDAzM2FkIi8+PHBhdGggZD0iTTI2OSwxMDguODNhMTQuODQsMTQuODQsMCwxLDAsMTkuOTQsNi41OEExNC44NiwxNC44NiwwLDAsMCwyNjksMTA4LjgzWiIgZmlsbD0iIzAwMzNhZCIvPjxwYXRoIGQ9Ik0xODYuNTUsMjAuNzZhMTAuMzksMTAuMzksMCwxLDAtOS43OS0xMUExMC4zOCwxMC4zOCwwLDAsMCwxODYuNTUsMjAuNzZaIiBmaWxsPSIjMDAzM2FkIi8+PHBhdGggZD0iTTE4Ni40Myw4Ni4xM2ExNC44NCwxNC44NCwwLDEsMC0xNC0xNS42NkExNC44NCwxNC44NCwwLDAsMCwxODYuNDMsODYuMTNaIiBmaWxsPSIjMDAzM2FkIi8+PHBhdGggZD0iTTEwNiwyMzcuNjhhMTQuODQsMTQuODQsMCwxLDAtMTkuOTMtNi41OEExNC44NSwxNC44NSwwLDAsMCwxMDYsMjM3LjY4WiIgZmlsbD0iIzAwMzNhZCIvPjxwYXRoIGQ9Ik0xOTYsMTA3Ljc5YTI1LjIyLDI1LjIyLDAsMSwwLDIxLjE0LTExLjQxQTI1LjI4LDI1LjI4LDAsMCwwLDE5NiwxMDcuNzlaIiBmaWxsPSIjMDAzM2FkIi8+PHBhdGggZD0iTTIzOS45MiwyMTMuMzdhMjUuMjYsMjUuMjYsMCwxLDAtMTEuMTgsMzMuOTFBMjUuMTEsMjUuMTEsMCwwLDAsMjM5LjkyLDIxMy4zN1oiIGZpbGw9IiMwMDMzYWQiLz48cGF0aCBkPSJNMjg0LDIxMS42MmExNC44NCwxNC44NCwwLDEsMCw0LjI3LDIwLjU1QTE0Ljg0LDE0Ljg0LDAsMCwwLDI4NCwyMTEuNjJaIiBmaWxsPSIjMDAzM2FkIi8+PHBhdGggZD0iTTMzMi4zOCwxNzMuNjhhMTIuNjIsMTIuNjIsMCwxLDAtMTMuMzEsMTEuODhBMTIuNjIsMTIuNjIsMCwwLDAsMzMyLjM4LDE3My42OFoiIGZpbGw9IiMwMDMzYWQiLz48cGF0aCBkPSJNMzY3LjMsMTY0LjcxYTguMTYsOC4xNiwwLDEsMCw3LjY5LDguNjFBOC4xNyw4LjE3LDAsMCwwLDM2Ny4zLDE2NC43MVoiIGZpbGw9IiMwMDMzYWQiLz48cGF0aCBkPSJNMzM0LjQyLDI0NS42OGExMC4zOSwxMC4zOSwwLDEsMCwzLDE0LjM5QTEwLjM5LDEwLjM5LDAsMCwwLDMzNC40MiwyNDUuNjhaIiBmaWxsPSIjMDAzM2FkIi8+PHBhdGggZD0iTTEwMi42NSwzMjEuOTRhOC4xNiw4LjE2LDAsMSwwLDIuMzQsMTEuM0E4LjE3LDguMTcsMCwwLDAsMTAyLjY1LDMyMS45NFoiIGZpbGw9IiMwMDMzYWQiLz48cGF0aCBkPSJNMjczLjgzLDMyMS4wOGE4LjE2LDguMTYsMCwxLDAsMTEsMy42MkE4LjE2LDguMTYsMCwwLDAsMjczLjgzLDMyMS4wOFoiIGZpbGw9IiMwMDMzYWQiLz48cGF0aCBkPSJNMTc5LDIzOC43MWEyNS4yNSwyNS4yNSwwLDEsMC0yMS4xNCwxMS40MUEyNS4xLDI1LjEsMCwwLDAsMTc5LDIzOC43MVoiIGZpbGw9IiMwMDMzYWQiLz48cGF0aCBkPSJNMTI4LjUzLDI3Ny40MWExMi42MiwxMi42MiwwLDEsMCwzLjYzLDE3LjQ3QTEyLjYyLDEyLjYyLDAsMCwwLDEyOC41MywyNzcuNDFaIiBmaWxsPSIjMDAzM2FkIi8+PHBhdGggZD0iTTE4Ny4zOCwzMjUuNzRhMTAuMzksMTAuMzksMCwxLDAsOS43OCwxMUExMC4zOSwxMC4zOSwwLDAsMCwxODcuMzgsMzI1Ljc0WiIgZmlsbD0iIzAwMzNhZCIvPjxwYXRoIGQ9Ik0xODcuNDksMjYwLjM3YTE0Ljg0LDE0Ljg0LDAsMSwwLDE0LDE1LjY3QTE0Ljg1LDE0Ljg1LDAsMCwwLDE4Ny40OSwyNjAuMzdaIiBmaWxsPSIjMDAzM2FkIi8+PHBhdGggZD0iTTI0OC4yMSwyNzYuNGExMi42MiwxMi42MiwwLDEsMCwxNyw1LjU5QTEyLjYyLDEyLjYyLDAsMCwwLDI0OC4yMSwyNzYuNFoiIGZpbGw9IiMwMDMzYWQiLz48L2c+PC9nPjwvc3ZnPg==
+|]
+
+platformSVG :: String
+platformSVG = [s|
+
+|]
+
+configSVG :: String
+configSVG = [s|
+
+|]
+
+startSVG :: String
+startSVG = [s|
+
+|]
+
+systemStartSVG :: String
+systemStartSVG = [s|
+
+|]
+
+uptimeSVG :: String
+uptimeSVG = [s|
+
+|]
+
+logsSVG :: String
+logsSVG = [s|
+
+|]
+
+directorySVG :: String
+directorySVG = [s|
+
+|]
+
+copySVG :: String
+copySVG = [s|
+
+|]
+
+serverSVG :: String
+serverSVG = [s|
+
+|]
+
+externalLinkSVG :: String
+externalLinkSVG = [s|
+
+|]
+
+externalLinkWhiteSVG :: String
+externalLinkWhiteSVG = [s|
+
+|]
+
+showSVG :: String
+showSVG = [s|
+
+|]
+
+hideSVG :: String
+hideSVG = [s|
+
+|]
+
+githubSVG :: String
+githubSVG = [s|
+
+|]
+
+refreshSVG :: String
+refreshSVG = [s|
+
+|]
+
+timeRangeSVG :: String
+timeRangeSVG = [s|
+
+|]
+
+leaderSVG :: String
+leaderSVG = [s|
+
+|]
+
+forgeSVG :: String
+forgeSVG = [s|
+
+|]
+
+notForgeSVG :: String
+notForgeSVG = [s|
+
+|]
+
+missedSVG :: String
+missedSVG = [s|
+
+|]
+
+eraSVG :: String
+eraSVG = [s|
+
+|]
+
+lengthSVG :: String
+lengthSVG = [s|
+
+|]
+
+endSVG :: String
+endSVG = [s|
+
+|]
+
+ekgMetricsSVG :: String
+ekgMetricsSVG = [s|
+
+|]
+
+certificateSVG :: String
+certificateSVG = [s|
+
+|]
+
+csvSVG :: String
+csvSVG = [s|
+
+|]
+
+deleteSVG :: String
+deleteSVG = [s|
+
+|]
+
+trashSVG :: String
+trashSVG = [s|
+
+|]
+
+sortSVG :: String
+sortSVG = [s|
+
+|]
+
+searchSVG :: String
+searchSVG = [s|
+
+|]
+
+eventsSVG :: String
+eventsSVG = [s|
+
+|]
+
+settingsSVG :: String
+settingsSVG = [s|
+
+|]
+
+rectangleSVG :: String
+rectangleSVG = [s|
+
+|]
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/JS/ChartJS.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/JS/ChartJS.hs
new file mode 100644
index 00000000000..1b08cc49874
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/JS/ChartJS.hs
@@ -0,0 +1,55 @@
+{-# LANGUAGE QuasiQuotes #-}
+
+module Cardano.Tracer.Handlers.RTView.UI.JS.ChartJS
+ ( chartJS
+ , chartJSLuxon
+ , chartJSAdapter
+ , chartJSPluginZoom
+ ) where
+
+import Data.String.QQ
+
+-- | To avoid run-time dependency from the static content, embed chart.js library in the page's header.
+chartJS :: String
+chartJS = [s|
+/*!
+ * Chart.js v3.7.1
+ * https://www.chartjs.org
+ * (c) 2022 Chart.js Contributors
+ * Released under the MIT License
+ */
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Chart=e()}(this,(function(){"use strict";const t="undefined"==typeof window?function(t){return t()}:window.requestAnimationFrame;function e(e,i,s){const n=s||(t=>Array.prototype.slice.call(t));let o=!1,a=[];return function(...s){a=n(s),o||(o=!0,t.call(window,(()=>{o=!1,e.apply(i,a)})))}}function i(t,e){let i;return function(...s){return e?(clearTimeout(i),i=setTimeout(t,e,s)):t.apply(this,s),e}}const s=t=>"start"===t?"left":"end"===t?"right":"center",n=(t,e,i)=>"start"===t?e:"end"===t?i:(e+i)/2,o=(t,e,i,s)=>t===(s?"left":"right")?i:"center"===t?(e+i)/2:e;var a=new class{constructor(){this._request=null,this._charts=new Map,this._running=!1,this._lastDate=void 0}_notify(t,e,i,s){const n=e.listeners[s],o=e.duration;n.forEach((s=>s({chart:t,initial:e.initial,numSteps:o,currentStep:Math.min(i-e.start,o)})))}_refresh(){this._request||(this._running=!0,this._request=t.call(window,(()=>{this._update(),this._request=null,this._running&&this._refresh()})))}_update(t=Date.now()){let e=0;this._charts.forEach(((i,s)=>{if(!i.running||!i.items.length)return;const n=i.items;let o,a=n.length-1,r=!1;for(;a>=0;--a)o=n[a],o._active?(o._total>i.duration&&(i.duration=o._total),o.tick(t),r=!0):(n[a]=n[n.length-1],n.pop());r&&(s.draw(),this._notify(s,i,t,"progress")),n.length||(i.running=!1,this._notify(s,i,t,"complete"),i.initial=!1),e+=n.length})),this._lastDate=t,0===e&&(this._running=!1)}_getAnims(t){const e=this._charts;let i=e.get(t);return i||(i={running:!1,initial:!0,items:[],listeners:{complete:[],progress:[]}},e.set(t,i)),i}listen(t,e,i){this._getAnims(t).listeners[e].push(i)}add(t,e){e&&e.length&&this._getAnims(t).items.push(...e)}has(t){return this._getAnims(t).items.length>0}start(t){const e=this._charts.get(t);e&&(e.running=!0,e.start=Date.now(),e.duration=e.items.reduce(((t,e)=>Math.max(t,e._duration)),0),this._refresh())}running(t){if(!this._running)return!1;const e=this._charts.get(t);return!!(e&&e.running&&e.items.length)}stop(t){const e=this._charts.get(t);if(!e||!e.items.length)return;const i=e.items;let s=i.length-1;for(;s>=0;--s)i[s].cancel();e.items=[],this._notify(t,e,Date.now(),"complete")}remove(t){return this._charts.delete(t)}};
+/*!
+ * @kurkle/color v0.1.9
+ * https://github.com/kurkle/color#readme
+ * (c) 2020 Jukka Kurkela
+ * Released under the MIT License
+ */const r={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},l="0123456789ABCDEF",h=t=>l[15&t],c=t=>l[(240&t)>>4]+l[15&t],d=t=>(240&t)>>4==(15&t);function u(t){var e=function(t){return d(t.r)&&d(t.g)&&d(t.b)&&d(t.a)}(t)?h:c;return t?"#"+e(t.r)+e(t.g)+e(t.b)+(t.a<255?e(t.a):""):t}function f(t){return t+.5|0}const g=(t,e,i)=>Math.max(Math.min(t,i),e);function p(t){return g(f(2.55*t),0,255)}function m(t){return g(f(255*t),0,255)}function x(t){return g(f(t/2.55)/100,0,1)}function b(t){return g(f(100*t),0,100)}const _=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;const y=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function v(t,e,i){const s=e*Math.min(i,1-i),n=(e,n=(e+t/30)%12)=>i-s*Math.max(Math.min(n-3,9-n,1),-1);return[n(0),n(8),n(4)]}function w(t,e,i){const s=(s,n=(s+t/60)%6)=>i-i*e*Math.max(Math.min(n,4-n,1),0);return[s(5),s(3),s(1)]}function M(t,e,i){const s=v(t,1,.5);let n;for(e+i>1&&(n=1/(e+i),e*=n,i*=n),n=0;n<3;n++)s[n]*=1-e-i,s[n]+=e;return s}function k(t){const e=t.r/255,i=t.g/255,s=t.b/255,n=Math.max(e,i,s),o=Math.min(e,i,s),a=(n+o)/2;let r,l,h;return n!==o&&(h=n-o,l=a>.5?h/(2-n-o):h/(n+o),r=n===e?(i-s)/h+(i0===e||isNaN(e)?0:e<0?Math.min(Math.round(e),-1):Math.max(Math.round(e),1);const x={second:500,minute:3e4,hour:18e5,day:432e5,week:3024e5,month:1296e6,quarter:5184e6,year:157248e5};function g(e,n,t,o=!1){const{min:a,max:i,options:c}=e,r=c.time&&c.time.round,l=x[r]||0,s=e.getValueForPixel(e.getPixelForValue(a+l)-n),m=e.getValueForPixel(e.getPixelForValue(i+l)-n),{min:u=-1/0,max:d=1/0}=o&&t&&t[e.axis]||{};return!!(isNaN(s)||isNaN(m)||sd)||h(e,{min:s,max:m},t,o)}function b(e,n,t){return g(e,n,t,!0)}const y={category:function(e,n,t,o){const a=d(e,n,t);return e.min===e.max&&n<1&&function(e){const n=e.getLabels().length-1;e.min>0&&(e.min-=1),e.max
"
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/EraSettings.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/EraSettings.hs
new file mode 100644
index 00000000000..13a8aafa4ab
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/EraSettings.hs
@@ -0,0 +1,51 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Cardano.Tracer.Handlers.RTView.Update.EraSettings
+ ( runEraSettingsUpdater
+ ) where
+
+import Control.Concurrent.STM.TVar (readTVarIO)
+import Control.Monad (forever, forM_)
+import Control.Monad.Extra (whenJust)
+import qualified Data.Map.Strict as M
+import Data.Set (Set)
+import qualified Data.Text as T
+import System.Time.Extra (sleep)
+
+import Cardano.Tracer.Handlers.RTView.State.EraSettings
+import Cardano.Tracer.Handlers.RTView.State.TraceObjects
+import Cardano.Tracer.Handlers.RTView.Update.Utils
+import Cardano.Tracer.Types
+
+runEraSettingsUpdater
+ :: ConnectedNodes
+ -> ErasSettings
+ -> SavedTraceObjects
+ -> IO ()
+runEraSettingsUpdater connectedNodes settings savedTO = forever $ do
+ connected <- readTVarIO connectedNodes
+ updateErasSettings connected settings savedTO
+ sleep 1.0
+
+updateErasSettings
+ :: Set NodeId
+ -> ErasSettings
+ -> SavedTraceObjects
+ -> IO ()
+updateErasSettings connected settings savedTO = do
+ savedTraceObjects <- readTVarIO savedTO
+ forM_ connected $ \nodeId ->
+ whenJust (M.lookup nodeId savedTraceObjects) $ \savedTOForNode ->
+ whenJust (M.lookup "Cardano.Node.Startup.ShelleyBased" savedTOForNode) $ \(trObValue, _, _) ->
+ -- Example: "Era Alonzo, Slot length 1s, Epoch length 432000, Slots per KESPeriod 129600"
+ case T.words $ T.replace "," "" trObValue of
+ [_, era, _, _, slotLen, _, _, epochLen, _, _, _, kesPeriod] ->
+ addEraSettings settings nodeId $
+ EraSettings
+ { esEra = era
+ , esSlotLengthInS = readInt (T.init slotLen) 0
+ , esEpochLength = readInt epochLen 0
+ , esKESPeriodLength = readInt kesPeriod 0
+ }
+ _ -> return ()
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Errors.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Errors.hs
new file mode 100644
index 00000000000..1c31998bf2d
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Errors.hs
@@ -0,0 +1,215 @@
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Cardano.Tracer.Handlers.RTView.Update.Errors
+ ( runErrorsUpdater
+ , updateNodesErrors
+ , searchErrorMessages
+ , deleteAllErrorMessages
+ , exitSearchMode
+ , sortErrorsByTime
+ , sortErrorsBySeverity
+ ) where
+
+import Control.Concurrent.STM.TVar
+import Control.Monad
+import Control.Monad.Extra (whenJust, whenJustM)
+import qualified Data.Map.Strict as M
+import qualified Data.Text as T
+import Data.Time.Format (defaultTimeLocale, formatTime)
+import qualified Graphics.UI.Threepenny as UI
+import Graphics.UI.Threepenny.Core
+import System.Time.Extra (sleep)
+import Text.Read (readMaybe)
+
+import Cardano.Logging (SeverityS (..))
+
+import Cardano.Tracer.Handlers.RTView.State.Errors
+import Cardano.Tracer.Handlers.RTView.State.TraceObjects
+import Cardano.Tracer.Handlers.RTView.UI.Img.Icons
+import Cardano.Tracer.Handlers.RTView.UI.JS.Utils
+import Cardano.Tracer.Handlers.RTView.UI.Utils
+import Cardano.Tracer.Handlers.RTView.Update.Utils
+import Cardano.Tracer.Types
+
+runErrorsUpdater
+ :: ConnectedNodes
+ -> Errors
+ -> SavedTraceObjects
+ -> IO ()
+runErrorsUpdater connectedNodes nodesErrors savedTO = forever $ do
+ sleep 2.0
+ connected <- readTVarIO connectedNodes
+ savedTraceObjects <- readTVarIO savedTO
+ forM_ connected $ \nodeId ->
+ whenJust (M.lookup nodeId savedTraceObjects) $ \savedTOForNode ->
+ forM_ (M.toList savedTOForNode) $ \(_, trObInfo@(_, severity, _)) ->
+ when (itIsError severity) $
+ addError nodesErrors nodeId trObInfo
+ where
+ itIsError sev =
+ case sev of
+ Error -> True
+ Critical -> True
+ Alert -> True
+ Emergency -> True
+ _ -> False
+
+-- | Update error messages in a corresponding modal window.
+updateNodesErrors
+ :: UI.Window
+ -> ConnectedNodes
+ -> Errors
+ -> UI ()
+updateNodesErrors window connectedNodes nodesErrors = do
+ connected <- liftIO $ readTVarIO connectedNodes
+ forM_ connected $ \nodeId@(NodeId anId) -> do
+ errorsFromNode <- liftIO $ getErrors nodesErrors nodeId
+ unless (null errorsFromNode) $ do
+ -- Update errors number (as it is in the state).
+ setTextValue (anId <> "__node-errors-num") (showT $ length errorsFromNode)
+ -- Enable 'Details' button.
+ findAndSet (set UI.enabled True) window (anId <> "__node-errors-details-button")
+ -- Add errors if needed.
+ whenJustM (UI.getElementById window (T.unpack anId <> "__node-errors-tbody")) $ \el ->
+ whenJustM (readMaybe <$> get dataState el) $ \(numberOfDisplayedRows :: Int) -> do
+ let onlyNewErrors = drop numberOfDisplayedRows errorsFromNode
+ doAddErrorRows nodeId onlyNewErrors el numberOfDisplayedRows
+
+doAddErrorRows
+ :: NodeId
+ -> [ErrorInfo]
+ -> Element
+ -> Int
+ -> UI ()
+doAddErrorRows nodeId errorsToAdd parentEl numberOfDisplayedRows = do
+ errorRows <-
+ forM errorsToAdd $ \(errorIx, (msg, sev, ts)) ->
+ mkErrorRow errorIx nodeId msg sev ts
+ -- Add them actually and remember their new number.
+ let newNumberOfDisplayedRows = numberOfDisplayedRows + length errorsToAdd
+ void $ element parentEl # set dataState (show newNumberOfDisplayedRows)
+ #+ errorRows
+ where
+ mkErrorRow _errorIx (NodeId anId) msg sev ts = do
+ copyErrorIcon <- image "has-tooltip-multiline has-tooltip-left rt-view-copy-icon" copySVG
+ # set dataTooltip "Click to copy this error"
+ on UI.click copyErrorIcon . const $
+ copyTextToClipboard $ errorToCopy ts sev msg
+
+ return $
+ UI.tr #. (T.unpack anId <> "-node-error-row") #+
+ [ UI.td #+
+ [ UI.span # set text (preparedTS ts)
+ ]
+ , UI.td #+
+ [ UI.span #. "tag is-medium is-danger" # set text (show sev)
+ ]
+ , UI.td #+
+ [ UI.p #. "control" #+
+ [ UI.input #. "input rt-view-error-msg-input"
+ # set UI.type_ "text"
+ # set (UI.attr "readonly") "readonly"
+ # set UI.value (T.unpack msg)
+ ]
+ ]
+ , UI.td #+
+ [ element copyErrorIcon
+ ]
+ ]
+
+ preparedTS = formatTime defaultTimeLocale "%b %e, %Y %T"
+
+ errorToCopy ts sev msg = "[" <> preparedTS ts <> "] [" <> show sev <> "] [" <> T.unpack msg <> "]"
+
+searchErrorMessages
+ :: UI.Window
+ -> Element
+ -> NodeId
+ -> Errors
+ -> UI.Timer
+ -> UI ()
+searchErrorMessages window searchInput nodeId@(NodeId anId) nodesErrors updateErrorsTimer = do
+ textToSearch <- T.strip . T.pack <$> get value searchInput
+ unless (T.null textToSearch) $ do
+ -- Ok, there is non-empty text we want to search. It means that now we are
+ -- in search/filter mode, and during this period the new messages shouldn't be added,
+ -- so we stop update timer temporarily.
+ UI.stop updateErrorsTimer
+ liftIO (getErrorsFilteredByText textToSearch nodesErrors nodeId) >>= \case
+ [] -> do
+ -- There is nothing found. So we have to inform the user that
+ -- there is no corresponding errors.
+ findByClassAndDo window (anId <> "-node-error-row") delete'
+ -- Reset number of currently displayed errors rows.
+ whenJustM (UI.getElementById window (T.unpack anId <> "__node-errors-tbody")) $ \el ->
+ void $ element el # set dataState "0"
+ foundErrors -> do
+ -- Delete displayed errors from window.
+ findByClassAndDo window (anId <> "-node-error-row") delete'
+ -- Do add found errors.
+ whenJustM (UI.getElementById window (T.unpack anId <> "__node-errors-tbody")) $ \el ->
+ doAddErrorRows nodeId foundErrors el 0
+
+deleteAllErrorMessages
+ :: UI.Window
+ -> NodeId
+ -> Errors
+ -> UI ()
+deleteAllErrorMessages window nodeId@(NodeId anId) nodesErrors = do
+ -- Delete errors from window.
+ findByClassAndDo window (anId <> "-node-error-row") delete'
+ -- Delete errors from state.
+ liftIO $ deleteAllErrors nodesErrors nodeId
+ -- Reset number of errors and disable Detail button.
+ setTextValue (anId <> "__node-errors-num") "0"
+ findAndSet (set UI.enabled False) window (anId <> "__node-errors-details-button")
+ -- Reset number of currently displayed errors rows.
+ whenJustM (UI.getElementById window (T.unpack anId <> "__node-errors-tbody")) $ \el ->
+ void $ element el # set dataState "0"
+
+exitSearchMode
+ :: UI.Window
+ -> NodeId
+ -> UI.Timer
+ -> UI ()
+exitSearchMode window (NodeId anId) updateErrorsTimer = do
+ -- Delete errors (last search result) from window.
+ findByClassAndDo window (anId <> "-node-error-row") delete'
+ -- Reset number of currently displayed errors rows.
+ whenJustM (UI.getElementById window (T.unpack anId <> "__node-errors-tbody")) $ \el ->
+ void $ element el # set dataState "0"
+ -- Start update errors timer again.
+ UI.start updateErrorsTimer
+
+sortErrorsByTime, sortErrorsBySeverity
+ :: UI.Window
+ -> NodeId
+ -> Element
+ -> Errors
+ -> UI ()
+sortErrorsByTime = sortErrors timeAsc timeDesc
+sortErrorsBySeverity = sortErrors severityAsc severityDesc
+
+sortErrors
+ :: (ErrorInfo -> ErrorInfo -> Ordering)
+ -> (ErrorInfo -> ErrorInfo -> Ordering)
+ -> UI.Window
+ -> NodeId
+ -> Element
+ -> Errors
+ -> UI ()
+sortErrors orderingAsc orderingDesc window nodeId@(NodeId anId) sortIcon nodesErrors = do
+ -- Delete errors from window.
+ findByClassAndDo window (anId <> "-node-error-row") delete'
+ get dataState sortIcon >>= \case
+ "desc" -> doSortErrors orderingAsc "asc"
+ _ -> doSortErrors orderingDesc "desc"
+ where
+ doSortErrors ordering orderState = do
+ sortedErrors <- liftIO $ getErrorsSortedBy ordering nodesErrors nodeId
+ -- Do add sorted errors.
+ whenJustM (UI.getElementById window (T.unpack anId <> "__node-errors-tbody")) $ \el ->
+ doAddErrorRows nodeId sortedErrors el 0
+ void $ element sortIcon # set dataState orderState
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Historical.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Historical.hs
new file mode 100644
index 00000000000..b2892ccb32f
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Historical.hs
@@ -0,0 +1,53 @@
+module Cardano.Tracer.Handlers.RTView.Update.Historical
+ ( runHistoricalUpdater
+ ) where
+
+import Control.Concurrent.STM.TVar (readTVarIO)
+import Control.Monad (forever, forM_)
+import qualified Data.Map.Strict as M
+import Data.Time.Clock.System
+import System.Time.Extra (sleep)
+
+import Cardano.Tracer.Handlers.Metrics.Utils
+import Cardano.Tracer.Handlers.RTView.State.Historical
+import Cardano.Tracer.Handlers.RTView.State.Last
+import Cardano.Tracer.Handlers.RTView.State.TraceObjects
+import Cardano.Tracer.Handlers.RTView.Update.Chain
+import Cardano.Tracer.Handlers.RTView.Update.Leadership
+import Cardano.Tracer.Handlers.RTView.Update.Resources
+import Cardano.Tracer.Handlers.RTView.Update.Transactions
+import Cardano.Tracer.Types
+
+-- | A lot of information received from the node is useful as historical data.
+-- It means that such an information should be displayed on time charts,
+-- where X axis is a time in UTC. An example: resource metrics, chain information,
+-- tx information, etc.
+--
+-- This information is extracted both from 'TraceObject's and 'EKG.Metrics' and then
+-- it will be saved as chart coords '[(ts, v)]', where 'ts' is a timestamp
+-- and 'v' is a value. Later, when the user will open RTView web-page, this
+-- saved data will be used to render historical charts.
+--
+-- It allows to collect historical data even when RTView web-page is closed.
+--
+runHistoricalUpdater
+ :: SavedTraceObjects
+ -> AcceptedMetrics
+ -> ResourcesHistory
+ -> LastResources
+ -> BlockchainHistory
+ -> TransactionsHistory
+ -> IO ()
+runHistoricalUpdater _savedTO acceptedMetrics resourcesHistory
+ lastResources chainHistory txHistory = forever $ do
+ sleep 1.0 -- TODO: should it be configured?
+
+ now <- systemToUTCTime <$> getSystemTime
+ allMetrics <- readTVarIO acceptedMetrics
+ forM_ (M.toList allMetrics) $ \(nodeId, (ekgStore, _)) -> do
+ metrics <- getListOfMetrics ekgStore
+ forM_ metrics $ \(metricName, metricValue) -> do
+ updateTransactionsHistory nodeId txHistory metricName metricValue now
+ updateResourcesHistory nodeId resourcesHistory lastResources metricName metricValue now
+ updateBlockchainHistory nodeId chainHistory metricName metricValue now
+ updateLeadershipHistory nodeId chainHistory metricName metricValue now
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/KES.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/KES.hs
new file mode 100644
index 00000000000..b0cd329bbf5
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/KES.hs
@@ -0,0 +1,58 @@
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Cardano.Tracer.Handlers.RTView.Update.KES
+ ( updateKESInfo
+ ) where
+
+import Control.Concurrent.STM.TVar (readTVarIO)
+import Control.Monad (forM_)
+import Control.Monad.Extra (whenJust)
+import qualified Data.Map.Strict as M
+import Data.Text (pack)
+import Data.Text.Read
+import qualified Graphics.UI.Threepenny as UI
+import Graphics.UI.Threepenny.Core
+import Text.Printf
+
+import Cardano.Tracer.Handlers.Metrics.Utils
+import Cardano.Tracer.Handlers.RTView.State.EraSettings
+import Cardano.Tracer.Handlers.RTView.State.Displayed
+import Cardano.Tracer.Handlers.RTView.UI.Utils
+import Cardano.Tracer.Types
+
+updateKESInfo
+ :: UI.Window
+ -> AcceptedMetrics
+ -> ErasSettings
+ -> DisplayedElements
+ -> UI ()
+updateKESInfo _window acceptedMetrics settings displayed = do
+ allMetrics <- liftIO $ readTVarIO acceptedMetrics
+ forM_ (M.toList allMetrics) $ \(nodeId@(NodeId anId), (ekgStore, _)) -> do
+ metrics <- liftIO $ getListOfMetrics ekgStore
+ forM_ metrics $ \(metricName, metricValue) ->
+ case metricName of
+ "cardano.node.currentKESPeriod" ->
+ setDisplayedValue nodeId displayed (anId <> "__node-current-kes-period") metricValue
+ "cardano.node.operationalCertificateExpiryKESPeriod" ->
+ setDisplayedValue nodeId displayed (anId <> "__node-op-cert-expiry-kes-period") metricValue
+ "cardano.node.operationalCertificateStartKESPeriod" ->
+ setDisplayedValue nodeId displayed (anId <> "__node-op-cert-start-kes-period") metricValue
+ "cardano.node.remainingKESPeriods" -> do
+ setDisplayedValue nodeId displayed (anId <> "__node-remaining-kes-periods") metricValue
+ allSettings <- liftIO $ readTVarIO settings
+ whenJust (M.lookup nodeId allSettings) $
+ setDaysUntilRenew nodeId metricValue
+ _ -> return ()
+ where
+ setDaysUntilRenew nodeId@(NodeId anId) metricValue EraSettings{esKESPeriodLength, esSlotLengthInS} = do
+ case decimal metricValue of
+ Left _ -> return ()
+ Right (remainingKesPeriods :: Int, _) -> do
+ let secondsUntilRenew = remainingKesPeriods * esKESPeriodLength * esSlotLengthInS
+ daysUntilRenew :: Double
+ daysUntilRenew = fromIntegral secondsUntilRenew / 3600 / 24
+ setDisplayedValue nodeId displayed (anId <> "__node-days-until-op-cert-renew") $
+ pack $ printf "%.1f" daysUntilRenew
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Leadership.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Leadership.hs
new file mode 100644
index 00000000000..4b274506a6d
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Leadership.hs
@@ -0,0 +1,68 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Cardano.Tracer.Handlers.RTView.Update.Leadership
+ ( updateLeadershipHistory
+ ) where
+
+import Data.Time.Clock
+
+import Cardano.Tracer.Handlers.Metrics.Utils
+import Cardano.Tracer.Handlers.RTView.State.Historical
+import Cardano.Tracer.Types
+
+updateLeadershipHistory
+ :: NodeId
+ -> BlockchainHistory
+ -> MetricName
+ -> MetricValue
+ -> UTCTime
+ -> IO ()
+updateLeadershipHistory nodeId (ChainHistory cHistory) metricName metricValue now =
+ case metricName of
+ -- Slot when the node was a leader, but couldn't forge the block.
+ "cardano.node.nodeCannotForge" -> updateNodeCannotForge
+ -- Slot when this node forged last block.
+ "cardano.node.forgedSlotLast" -> updateForgedSlotLast
+ -- Slot when this node is leader.
+ "cardano.node.nodeIsLeader" -> updateNodeIsLeader
+ -- Slot when this node made leadership check and concludes it's not leader.
+ "cardano.node.nodeNotLeader" -> updateNodeIsNotLeader
+ -- Slot when invalid block was forged.
+ "cardano.node.forgedInvalidSlotLast" -> updateForgedInvalidSlotLast
+ -- Slot when the node adopted the block it forged.
+ "cardano.node.adoptedSlotLast" -> updateAdoptedSlotLast
+ -- Slot when the node didn't adopted the block it forged, but the block was valid.
+ "cardano.node.notAdoptedSlotLast" -> updateNotAdoptedSlotLast
+ -- Slot when the leadership check is started.
+ "cardano.node.aboutToLeadSlotLast" -> updateAboutToLeadSlotLast
+ -- Slot when the leadership check is failed.
+ "cardano.node.couldNotForgeSlotLast" -> updateCouldNotForgeSlotLast
+ _ -> return ()
+ where
+ updateNodeCannotForge =
+ readValueI metricValue $ addHistoricalData cHistory nodeId now NodeCannotForgeData
+
+ updateForgedSlotLast =
+ readValueI metricValue $ addHistoricalData cHistory nodeId now ForgedSlotLastData
+
+ updateNodeIsLeader =
+ readValueI metricValue $ addHistoricalData cHistory nodeId now NodeIsLeaderData
+
+ updateNodeIsNotLeader =
+ readValueI metricValue $ addHistoricalData cHistory nodeId now NodeIsNotLeaderData
+
+ updateForgedInvalidSlotLast =
+ readValueI metricValue $ addHistoricalData cHistory nodeId now ForgedInvalidSlotLastData
+
+ updateAdoptedSlotLast =
+ readValueI metricValue $ addHistoricalData cHistory nodeId now AdoptedSlotLastData
+
+ updateNotAdoptedSlotLast =
+ readValueI metricValue $ addHistoricalData cHistory nodeId now NotAdoptedSlotLastData
+
+ updateAboutToLeadSlotLast =
+ readValueI metricValue $ addHistoricalData cHistory nodeId now AboutToLeadSlotLastData
+
+ updateCouldNotForgeSlotLast =
+ readValueI metricValue $ addHistoricalData cHistory nodeId now CouldNotForgeSlotLastData
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/NodeInfo.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/NodeInfo.hs
new file mode 100644
index 00000000000..e2aff327a42
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/NodeInfo.hs
@@ -0,0 +1,73 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Cardano.Tracer.Handlers.RTView.Update.NodeInfo
+ ( askNSetNodeInfo
+ ) where
+
+import Control.Monad (forM_, unless)
+import Control.Monad.Extra (whenJustM)
+import Data.Set (Set)
+import qualified Data.Set as S
+import qualified Data.Text as T
+import Data.Time.Format (defaultTimeLocale, formatTime)
+import qualified Graphics.UI.Threepenny as UI
+import Graphics.UI.Threepenny.Core
+
+import Cardano.Node.Startup (NodeInfo (..))
+
+import Cardano.Tracer.Handlers.RTView.State.Displayed
+import Cardano.Tracer.Handlers.RTView.UI.Utils
+import Cardano.Tracer.Handlers.RTView.Update.Utils
+import Cardano.Tracer.Types
+
+askNSetNodeInfo
+ :: UI.Window
+ -> DataPointRequestors
+ -> Set NodeId
+ -> DisplayedElements
+ -> UI ()
+askNSetNodeInfo window dpRequestors newlyConnected displayedElements =
+ unless (S.null newlyConnected) $
+ forM_ newlyConnected $ \nodeId@(NodeId anId) ->
+ whenJustM (liftIO $ askDataPoint dpRequestors nodeId "NodeInfo") $ \ni -> do
+ let nodeNameElId = anId <> "__node-name"
+ shortName = shortenName $ niName ni
+
+ setTextValues
+ [ (nodeNameElId, shortName)
+ , (anId <> "__node-version", niVersion ni)
+ , (anId <> "__node-commit", T.take 7 $ niCommit ni)
+ , (anId <> "__node-name-for-peers", shortName)
+ , (anId <> "__node-name-for-ekg-metrics", shortName)
+ , (anId <> "__node-name-for-errors", shortName)
+ ]
+
+ findAndSet (set UI.href $ nodeLink (niCommit ni)) window (anId <> "__node-commit")
+
+ setProtocol (niProtocol ni) (anId <> "__node-protocol")
+
+ let nodeStartElId = anId <> "__node-start-time"
+ setTime (niStartTime ni) nodeStartElId
+ setTime (niSystemStartTime ni) (anId <> "__node-system-start-time")
+
+ liftIO $ saveDisplayedValue displayedElements nodeId nodeStartElId (T.pack . show $ niStartTime ni)
+ liftIO $ saveDisplayedValue displayedElements nodeId nodeNameElId (niName ni)
+ where
+ nodeLink commit = T.unpack $ "https://github.com/input-output-hk/cardano-node/commit/" <> T.take 7 commit
+
+ setProtocol p id' = do
+ justCleanText id'
+ let byronTag = UI.span #. "tag is-warning is-rounded is-medium" # set text "Byron"
+ shelleyTag = UI.span #. "tag is-info is-rounded is-medium ml-3" # set text "Shelley"
+ case p of
+ "Byron" -> findAndAdd [byronTag] window id'
+ "Shelley" -> findAndAdd [shelleyTag] window id'
+ _ -> findAndAdd [byronTag, shelleyTag] window id'
+
+ setTime ts id' = do
+ justCleanText id'
+ let time = formatTime defaultTimeLocale "%b %e, %Y %T" ts
+ tz = formatTime defaultTimeLocale "%Z" ts
+ findAndAdd [ string time
+ , UI.span #. "has-text-weight-normal is-size-6 ml-2" # set text tz
+ ] window id'
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/NodeState.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/NodeState.hs
new file mode 100644
index 00000000000..dfffcb1787f
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/NodeState.hs
@@ -0,0 +1,44 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Cardano.Tracer.Handlers.RTView.Update.NodeState
+ ( askNSetNodeState
+ ) where
+
+import Control.Concurrent.STM.TVar
+import Control.Monad (forM_)
+import Control.Monad.Extra (whenJustM)
+import Data.Text (pack)
+import qualified Graphics.UI.Threepenny as UI
+import Graphics.UI.Threepenny.Core
+import Text.Printf
+
+import Cardano.Node.Tracing.StateRep
+
+import Cardano.Tracer.Handlers.RTView.State.Displayed
+import Cardano.Tracer.Handlers.RTView.UI.Utils
+import Cardano.Tracer.Handlers.RTView.Update.Utils
+import Cardano.Tracer.Types
+
+-- | There is 'NodeState' datapoint, it contains different information
+-- about the current state of the node. For example, its sync progress.
+askNSetNodeState
+ :: UI.Window
+ -> ConnectedNodes
+ -> DataPointRequestors
+ -> DisplayedElements
+ -> UI ()
+askNSetNodeState _window connectedNodes dpRequestors displayed = do
+ connected <- liftIO $ readTVarIO connectedNodes
+ forM_ connected $ \nodeId@(NodeId _anId) ->
+ whenJustM (liftIO $ askDataPoint dpRequestors nodeId "NodeState") $ \(ns :: NodeState) ->
+ case ns of
+ NodeAddBlock (AddedToCurrentChain _ _ syncPct) -> setSyncProgress nodeId syncPct
+ _ -> return ()
+ where
+ setSyncProgress nodeId@(NodeId anId) syncPct = do
+ let nodeSyncProgressElId = anId <> "__node-sync-progress"
+ if syncPct < 100.0
+ then setDisplayedValue nodeId displayed nodeSyncProgressElId $
+ pack (printf "%.2f" syncPct) <> " %"
+ else setTextAndClasses nodeSyncProgressElId "100 %" "rt-view-percent-done"
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Nodes.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Nodes.hs
new file mode 100644
index 00000000000..f968e832551
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Nodes.hs
@@ -0,0 +1,320 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Cardano.Tracer.Handlers.RTView.Update.Nodes
+ ( addColumnsForConnected
+ , addDatasetsForConnected
+ , checkNoNodesState
+ , updateNodesUI
+ , updateNodesUptime
+ ) where
+
+import Control.Concurrent.STM (atomically)
+import Control.Concurrent.STM.TVar
+import Control.Monad (forM_, unless, when)
+import Control.Monad.Extra (whenJust)
+import Data.List.NonEmpty (NonEmpty)
+import qualified Data.Map.Strict as M
+import Data.Maybe (fromMaybe, catMaybes)
+import Data.Set (Set, (\\))
+import qualified Data.Set as S
+import qualified Data.Text as T
+import Data.Text.Read
+import Data.Time.Calendar
+import Data.Time.Clock
+import Data.Time.Clock.System
+import Data.Time.Format (defaultTimeLocale, formatTime)
+import Data.Word (Word64)
+import qualified Graphics.UI.Threepenny as UI
+import Graphics.UI.Threepenny.Core
+import Text.Read (readMaybe)
+
+import Cardano.Tracer.Configuration
+import Cardano.Tracer.Handlers.Metrics.Utils
+import Cardano.Tracer.Handlers.RTView.State.EraSettings
+import Cardano.Tracer.Handlers.RTView.State.Errors
+import Cardano.Tracer.Handlers.RTView.State.Displayed
+import Cardano.Tracer.Handlers.RTView.State.TraceObjects
+import Cardano.Tracer.Handlers.RTView.UI.HTML.Node.Column
+import Cardano.Tracer.Handlers.RTView.UI.Charts
+import Cardano.Tracer.Handlers.RTView.UI.Types
+import Cardano.Tracer.Handlers.RTView.UI.Utils
+import Cardano.Tracer.Handlers.RTView.Update.NodeInfo
+import Cardano.Tracer.Handlers.RTView.Update.Utils
+import Cardano.Tracer.Types
+
+updateNodesUI
+ :: UI.Window
+ -> ConnectedNodes
+ -> DisplayedElements
+ -> AcceptedMetrics
+ -> SavedTraceObjects
+ -> ErasSettings
+ -> DataPointRequestors
+ -> NonEmpty LoggingParams
+ -> Colors
+ -> DatasetsIndices
+ -> Errors
+ -> UI.Timer
+ -> UI ()
+updateNodesUI window connectedNodes displayedElements acceptedMetrics savedTO nodesEraSettings
+ dpRequestors loggingConfig colors datasetIndices nodesErrors updateErrorsTimer = do
+ (connected, displayedEls) <- liftIO . atomically $ (,)
+ <$> readTVar connectedNodes
+ <*> readTVar displayedElements
+ -- Check connected/disconnected nodes since previous UI's update.
+ let displayed = S.fromList $ M.keys displayedEls
+ when (connected /= displayed) $ do
+ let disconnected = displayed \\ connected -- In 'displayed' but not in 'connected'.
+ newlyConnected = connected \\ displayed -- In 'connected' but not in 'displayed'.
+ deleteColumnsForDisconnected window connected disconnected
+ addColumnsForConnected window newlyConnected loggingConfig nodesErrors updateErrorsTimer
+ checkNoNodesState window connected
+ askNSetNodeInfo window dpRequestors newlyConnected displayedElements
+ addDatasetsForConnected window newlyConnected colors datasetIndices displayedElements
+ liftIO $ updateDisplayedElements displayedElements connected
+ setBlockReplayProgress connected displayedElements acceptedMetrics
+ setChunkValidationProgress connected savedTO
+ setLedgerDBProgress connected savedTO
+ setLeadershipStats connected displayedElements acceptedMetrics
+ setEraEpochInfo connected displayedElements acceptedMetrics nodesEraSettings
+
+addColumnsForConnected
+ :: UI.Window
+ -> Set NodeId
+ -> NonEmpty LoggingParams
+ -> Errors
+ -> UI.Timer
+ -> UI ()
+addColumnsForConnected window newlyConnected loggingConfig nodesErrors updateErrorsTimer = do
+ unless (S.null newlyConnected) $
+ findAndShow window "main-table-container"
+ forM_ newlyConnected $
+ addNodeColumn window loggingConfig nodesErrors updateErrorsTimer
+
+addDatasetsForConnected
+ :: UI.Window
+ -> Set NodeId
+ -> Colors
+ -> DatasetsIndices
+ -> DisplayedElements
+ -> UI ()
+addDatasetsForConnected window newlyConnected colors datasetIndices displayedElements = do
+ unless (S.null newlyConnected) $
+ findAndShow window "main-charts-container"
+ forM_ newlyConnected $ \nodeId ->
+ addNodeDatasetsToCharts window nodeId colors datasetIndices displayedElements
+
+deleteColumnsForDisconnected
+ :: UI.Window
+ -> Set NodeId
+ -> Set NodeId
+ -> UI ()
+deleteColumnsForDisconnected window connected disconnected = do
+ forM_ disconnected $ deleteNodeColumn window
+ when (S.null connected) $ do
+ findAndHide window "main-table-container"
+ findAndHide window "main-charts-container"
+ -- Please note that we don't remove historical data from charts
+ -- for disconnected node. Because the user may want to see the
+ -- historical data even for the node that already disconnected.
+
+checkNoNodesState :: UI.Window -> Set NodeId -> UI ()
+checkNoNodesState window connected =
+ if S.null connected
+ then do
+ findAndShow window "no-nodes"
+ findAndShow window "no-nodes-info"
+ else do
+ findAndHide window "no-nodes"
+ findAndHide window "no-nodes-info"
+
+updateNodesUptime
+ :: ConnectedNodes
+ -> DisplayedElements
+ -> UI ()
+updateNodesUptime connectedNodes displayedElements = do
+ connected <- liftIO $ readTVarIO connectedNodes
+ now <- systemToUTCTime <$> liftIO getSystemTime
+ displayed <- liftIO $ readTVarIO displayedElements
+ let elsIdsWithUptimes = map (getUptimeForNode now displayed) $ S.toList connected
+ setTextValues $ catMaybes elsIdsWithUptimes
+ where
+ getUptimeForNode now displayed nodeId@(NodeId anId) =
+ let nodeStartElId = anId <> "__node-start-time"
+ nodeUptimeElId = anId <> "__node-uptime"
+ in case getDisplayedValuePure displayed nodeId nodeStartElId of
+ Nothing -> Nothing
+ Just tsRaw ->
+ case readMaybe (T.unpack tsRaw) of
+ Nothing -> Nothing
+ Just (startTime :: UTCTime) ->
+ let uptimeDiff = now `diffUTCTime` startTime
+ uptime = uptimeDiff `addUTCTime` nullTime
+ uptimeFormatted = formatTime defaultTimeLocale "%X" uptime
+ daysNum = utctDay uptime `diffDays` utctDay nullTime
+ uptimeWithDays = if daysNum > 0
+ -- Show days only if 'uptime' > 23:59:59.
+ then show daysNum <> "d " <> uptimeFormatted
+ else uptimeFormatted
+ in Just (nodeUptimeElId, T.pack uptimeWithDays)
+
+setBlockReplayProgress
+ :: Set NodeId
+ -> DisplayedElements
+ -> AcceptedMetrics
+ -> UI ()
+setBlockReplayProgress connected _displayedElements acceptedMetrics = do
+ allMetrics <- liftIO $ readTVarIO acceptedMetrics
+ forM_ connected $ \nodeId ->
+ whenJust (M.lookup nodeId allMetrics) $ \(ekgStore, _) -> do
+ metrics <- liftIO $ getListOfMetrics ekgStore
+ whenJust (lookup "Block replay progress (%)" metrics) $ \metricValue ->
+ updateBlockReplayProgress nodeId metricValue
+ where
+ updateBlockReplayProgress (NodeId anId) mValue =
+ case double mValue of
+ Left _ -> return ()
+ Right (progressPct, _) -> do
+ let nodeBlockReplayElId = anId <> "__node-block-replay"
+ progressPctS = T.pack $ show progressPct
+ if "100" `T.isInfixOf` progressPctS
+ then setTextAndClasses nodeBlockReplayElId "100 %" "rt-view-percent-done"
+ else setTextValue nodeBlockReplayElId $ progressPctS <> " %"
+
+setChunkValidationProgress
+ :: Set NodeId
+ -> SavedTraceObjects
+ -> UI ()
+setChunkValidationProgress connected savedTO = do
+ savedTraceObjects <- liftIO $ readTVarIO savedTO
+ forM_ connected $ \nodeId@(NodeId anId) ->
+ whenJust (M.lookup nodeId savedTraceObjects) $ \savedTOForNode -> do
+ let nodeChunkValidationElId = anId <> "__node-chunk-validation"
+ forM_ (M.toList savedTOForNode) $ \(namespace, (trObValue, _, _)) ->
+ case namespace of
+ "Cardano.Node.ChainDB.ImmDbEvent.ChunkValidation.ValidatedChunk" ->
+ -- In this case we don't need to check if the value differs from displayed one,
+ -- because this 'TraceObject' is forwarded only with new values, and after 100%
+ -- the node doesn't forward it anymore.
+ --
+ -- Example: "Validated chunk no. 2262 out of 2423. Progress: 93.36%"
+ case T.words trObValue of
+ [_, _, _, current, _, _, from, _, progressPct] ->
+ setTextValue nodeChunkValidationElId $
+ T.init progressPct <> " %: no. " <> current <> " from " <> T.init from
+ _ -> return ()
+ "Cardano.Node.ChainDB.ImmDbEvent.ValidatedLastLocation" ->
+ setTextAndClasses nodeChunkValidationElId "100 %" "rt-view-percent-done"
+ _ -> return ()
+
+setLedgerDBProgress
+ :: Set NodeId
+ -> SavedTraceObjects
+ -> UI ()
+setLedgerDBProgress connected savedTO = do
+ savedTraceObjects <- liftIO $ readTVarIO savedTO
+ forM_ connected $ \nodeId@(NodeId anId) ->
+ whenJust (M.lookup nodeId savedTraceObjects) $ \savedTOForNode -> do
+ let nodeLedgerDBUpdateElId = anId <> "__node-update-ledger-db"
+ forM_ (M.toList savedTOForNode) $ \(namespace, (trObValue, _, _)) ->
+ case namespace of
+ "Cardano.Node.ChainDB.InitChainSelEvent.UpdateLedgerDb" ->
+ -- In this case we don't need to check if the value differs from displayed one,
+ -- because this 'TraceObject' is forwarded only with new values, and after 100%
+ -- the node doesn't forward it anymore.
+ --
+ -- Example: "Pushing ledger state for block b1e6...fc5a at slot 54495204. Progress: 3.66%"
+ case T.words trObValue of
+ [_, _, _, _, _, _, _, _, _, _, progressPct] -> do
+ if "100" `T.isInfixOf` progressPct
+ then setTextAndClasses nodeLedgerDBUpdateElId "100 %" "rt-view-percent-done"
+ else setTextValue nodeLedgerDBUpdateElId $ T.init progressPct <> " %"
+ _ -> return ()
+ _ -> return ()
+
+setLeadershipStats
+ :: Set NodeId
+ -> DisplayedElements
+ -> AcceptedMetrics
+ -> UI ()
+setLeadershipStats connected displayed acceptedMetrics = do
+ allMetrics <- liftIO $ readTVarIO acceptedMetrics
+ forM_ connected $ \nodeId@(NodeId anId) ->
+ whenJust (M.lookup nodeId allMetrics) $ \(ekgStore, _) -> do
+ metrics <- liftIO $ getListOfMetrics ekgStore
+ forM_ metrics $ \(mName, mValue) ->
+ case mName of
+ -- How many times this node was a leader.
+ "nodeIsLeaderNum" -> setDisplayedValue nodeId displayed (anId <> "__node-leadership") mValue
+ -- How many blocks were forged by this node.
+ "blocksForgedNum" -> setDisplayedValue nodeId displayed (anId <> "__node-forged-blocks") mValue
+ -- How many times this node could not forge.
+ "nodeCannotForgeNum" -> setDisplayedValue nodeId displayed (anId <> "__node-cannot-forge") mValue
+ -- How many slots were missed in this node.
+ "slotsMissed" -> setDisplayedValue nodeId displayed (anId <> "__node-missed-slots") mValue
+ _ -> return ()
+
+setEraEpochInfo
+ :: Set NodeId
+ -> DisplayedElements
+ -> AcceptedMetrics
+ -> ErasSettings
+ -> UI ()
+setEraEpochInfo connected displayed acceptedMetrics nodesEraSettings = do
+ allSettings <- liftIO $ readTVarIO nodesEraSettings
+ allMetrics <- liftIO $ readTVarIO acceptedMetrics
+ forM_ connected $ \nodeId@(NodeId anId) ->
+ whenJust (M.lookup nodeId allSettings) $ \settings -> do
+ setDisplayedValue nodeId displayed (anId <> "__node-era") $ esEra settings
+ whenJust (M.lookup nodeId allMetrics) $ \(ekgStore, _) -> do
+ metrics <- liftIO $ getListOfMetrics ekgStore
+ let epoch = fromMaybe "" $ lookup "cardano.node.epoch" metrics
+ slotInEpoch = fromMaybe "" $ lookup "cardano.node.slotInEpoch" metrics
+ updateEpochInfo settings nodeId epoch slotInEpoch
+ where
+ updateEpochInfo settings nodeId@(NodeId anId) epochS slotInEpochS =
+ unless (T.null epochS || T.null slotInEpochS) $ do
+ let epochNum = readInt epochS 0
+ _slotInEpoch = readInt slotInEpochS 0
+ setDisplayedValue nodeId displayed (anId <> "__node-epoch-num") epochS
+ case getEndOfCurrentEpoch settings epochNum of
+ Nothing -> return ()
+ Just (_start, end) -> do
+ setTextValue (anId <> "__node-epoch-end") $
+ T.pack $ formatTime defaultTimeLocale "%D %T" end
+ {-
+ let elapsedSecondsFromEpochStart = nesSlotLengthInS settings * slotInEpoch
+ diffFromEndToStart = end `diffUTCTime` start
+ elapsed = secondsToNominalDiffTime (fromIntegral elapsedSecondsFromEpochStart)
+ diffFromNowToEnd = diffFromEndToStart - elapsed
+ timeLeft = diffFromNowToEnd `addUTCTime` nullTime
+ timeLeftF = T.pack $ formatTime defaultTimeLocale "%d:%H:%M:%S" timeLeft
+ setTextValue (anId <> "__node-epoch-end") timeLeftF
+ -}
+
+ getEndOfCurrentEpoch EraSettings{esEra, esSlotLengthInS, esEpochLength} currentEpoch =
+ case lookup esEra epochsInfo of
+ Nothing -> Nothing
+ Just (epochStartDate, firstEpochInEra) ->
+ let elapsedEpochsInEra = currentEpoch - firstEpochInEra
+ epochLengthInS = esSlotLengthInS * esEpochLength
+ secondsFromEpochStartToEpoch = epochLengthInS * elapsedEpochsInEra
+ !dateOfEpochStart = epochStartDate + fromIntegral secondsFromEpochStartToEpoch
+ !dateOfEpochEnd = dateOfEpochStart + fromIntegral epochLengthInS
+ in Just (s2utc dateOfEpochStart, s2utc dateOfEpochEnd)
+
+type EraName = T.Text
+type FirstEpochInEra = Int
+type EraStartPOSIX = Word64
+
+-- It is taken from 'cardano-ledger' wiki topic "First-Block-of-Each-Era".
+epochsInfo :: [(EraName, (EraStartPOSIX, FirstEpochInEra))]
+epochsInfo =
+ [ ("Shelley", (1596073491, 208)) -- 07/30/2020 1:44:51 AM GMT
+ , ("Allegra", (1608169491, 236)) -- 12/17/2020 1:44:51 AM GMT
+ , ("Mary", (1614649491, 251)) -- 03/02/2021 1:44:51 AM GMT
+ , ("Alonzo", (1634953491, 298)) -- 10/23/2021 1:44:51 AM GMT, start of new protocol.
+ ]
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Peers.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Peers.hs
new file mode 100644
index 00000000000..7588a468b42
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Peers.hs
@@ -0,0 +1,146 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Cardano.Tracer.Handlers.RTView.Update.Peers
+ ( updateNodesPeers
+ ) where
+
+import Control.Concurrent.STM.TVar
+import Control.Monad
+import Control.Monad.Extra (whenJustM)
+import Data.List (find)
+import Data.List.Extra (notNull)
+import Data.Maybe (mapMaybe)
+import qualified Data.Map.Strict as M
+import Data.Set ((\\))
+import qualified Data.Set as S
+import Data.Text (Text, unpack)
+import qualified Data.Text as T
+import qualified Graphics.UI.Threepenny as UI
+import Graphics.UI.Threepenny.Core
+
+import Cardano.Tracer.Handlers.RTView.State.Peers
+import Cardano.Tracer.Handlers.RTView.State.TraceObjects
+import Cardano.Tracer.Handlers.RTView.UI.HTML.Node.Peers
+import Cardano.Tracer.Handlers.RTView.UI.Utils
+import Cardano.Tracer.Handlers.RTView.Update.Utils
+import Cardano.Tracer.Types
+
+updateNodesPeers
+ :: UI.Window
+ -> Peers
+ -> SavedTraceObjects
+ -> UI ()
+updateNodesPeers window displayedPeers savedTO = do
+ savedTraceObjects <- liftIO $ readTVarIO savedTO
+ forM_ (M.toList savedTraceObjects) $ \(nodeId, savedTOForNode) ->
+ forM_ (M.toList savedTOForNode) $ \(namespace, (trObValue, _, _)) ->
+ case namespace of
+ "Cardano.Node.Peers" ->
+ doUpdatePeers window nodeId displayedPeers trObValue
+ _ -> return ()
+
+doUpdatePeers
+ :: UI.Window
+ -> NodeId
+ -> Peers
+ -> Text
+ -> UI ()
+doUpdatePeers window nodeId@(NodeId anId) displayedPeers trObValue =
+ if "NodeKernelPeers" `T.isInfixOf` trObValue
+ then return () -- It was empty 'TraceObject' (without useful info), ignore it.
+ else do
+ -- Update peers number.
+ setTextValue (anId <> "__node-peers-num") (showT (length peersParts))
+ -- If there is at least one connected peer, we enable 'Details' button.
+ findAndSet (set UI.enabled $ notNull peersParts)
+ window $ anId <> "__node-peers-details-button"
+ -- Update particular info about peers.
+ let connectedPeers = getConnectedPeers
+ connectedPeersAddresses = getConnectedPeersAddresses
+ displayedPeersAddresses <- liftIO $ getPeersAddresses displayedPeers nodeId
+ if displayedPeersAddresses /= connectedPeersAddresses
+ then do
+ -- There are some changes with number of peers: some new were connected
+ -- and/or some displayed ones were disconnected.
+ let disconnectedPeers = displayedPeersAddresses \\ connectedPeersAddresses -- Not in connected
+ newlyConnectedPeers = connectedPeersAddresses \\ displayedPeersAddresses -- Not in displayed
+ deleteRowsForDisconnected disconnectedPeers
+ addRowsForNewlyConnected newlyConnectedPeers connectedPeers
+ else
+ -- No changes with number of peers, only their data was changed.
+ updateConnectedPeersData connectedPeers
+ where
+ peersParts = T.splitOn "," trObValue
+
+ getConnectedPeers = S.fromList $
+ mapMaybe
+ (\peerPart -> let peerData = T.words peerPart in
+ if length peerData == 6 then Just peerData else Nothing
+ ) peersParts
+
+ getConnectedPeersAddresses = S.map head getConnectedPeers
+
+ deleteRowsForDisconnected disconnected =
+ forM_ disconnected $ \peerAddr -> do
+ deletePeerRow window nodeId peerAddr
+ liftIO $ removePeer displayedPeers nodeId peerAddr
+
+ addRowsForNewlyConnected newlyConnectedPeers connectedPeers =
+ forM_ newlyConnectedPeers $ \peerAddr -> do
+ case find (\peerDataList -> head peerDataList == peerAddr) connectedPeers of
+ Just [_, status, slotNo, reqsInF, blocksInF, bytesInF] -> do
+ let idPrefix = anId <> peerAddr
+ addPeerRow idPrefix peerAddr status slotNo reqsInF blocksInF bytesInF
+ liftIO $ addPeer displayedPeers nodeId peerAddr
+ _ -> return ()
+
+ addPeerRow idPrefix peerAddr status slotNo reqsInF blocksInF bytesInF = do
+ let idPrefix' = unpack idPrefix
+ whenJustM (UI.getElementById window (unpack anId <> "__node-peers-tbody")) $ \el ->
+ void $ element el #+
+ [ UI.tr ## (idPrefix' <> "__node-peer-row") #+
+ [ UI.td #+
+ [ UI.span ## (idPrefix' <> "__address")
+ #. "is-family-monospace"
+ # set text (unpack peerAddr)
+ ]
+ , UI.td #+
+ [ UI.span ## (idPrefix' <> "__status")
+ # set text (unpack status)
+ ]
+ , UI.td #+
+ [ UI.span ## (idPrefix' <> "__slotNo")
+ # set text (unpack $ checkSlot slotNo)
+ ]
+ , UI.td #+
+ [ UI.span ## (idPrefix' <> "__reqsInF")
+ # set text (unpack reqsInF)
+ ]
+ , UI.td #+
+ [ UI.span ## (idPrefix' <> "__blocksInF")
+ # set text (unpack blocksInF)
+ ]
+ , UI.td #+
+ [ UI.span ## (idPrefix' <> "__bytesInF")
+ # set text (unpack bytesInF)
+ ]
+ ]
+ ]
+
+ updateConnectedPeersData connectedPeers = do
+ let allPeersData = concatMap collectDataToUpdate (S.toList connectedPeers)
+ -- Update values for all peers by one single FFI-call.
+ setTextValues allPeersData
+
+ collectDataToUpdate [peerAddr, status, slotNo, reqsInF, blocksInF, bytesInF] =
+ let idPrefix = anId <> peerAddr
+ in [ (idPrefix <> "__status", status)
+ , (idPrefix <> "__slotNo", checkSlot slotNo)
+ , (idPrefix <> "__reqsInF", reqsInF)
+ , (idPrefix <> "__blocksInF", blocksInF)
+ , (idPrefix <> "__bytesInF", bytesInF)
+ ]
+ collectDataToUpdate _ = []
+
+ checkSlot slotNo = if slotNo == "???" then "—" else slotNo
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Reload.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Reload.hs
new file mode 100644
index 00000000000..ef7ab2daf26
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Reload.hs
@@ -0,0 +1,41 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Cardano.Tracer.Handlers.RTView.Update.Reload
+ ( updateUIAfterReload
+ ) where
+
+import Control.Concurrent.STM.TVar
+import Data.List.NonEmpty (NonEmpty)
+import qualified Graphics.UI.Threepenny as UI
+import Graphics.UI.Threepenny.Core
+
+import Cardano.Tracer.Configuration
+import Cardano.Tracer.Handlers.RTView.State.Displayed
+import Cardano.Tracer.Handlers.RTView.State.Errors
+import Cardano.Tracer.Handlers.RTView.UI.Types
+import Cardano.Tracer.Handlers.RTView.Update.NodeInfo
+import Cardano.Tracer.Handlers.RTView.Update.Nodes
+import Cardano.Tracer.Types
+
+updateUIAfterReload
+ :: UI.Window
+ -> ConnectedNodes
+ -> DisplayedElements
+ -> DataPointRequestors
+ -> NonEmpty LoggingParams
+ -> Colors
+ -> DatasetsIndices
+ -> Errors
+ -> UI.Timer
+ -> UI ()
+updateUIAfterReload window connectedNodes displayedElements dpRequestors
+ loggingConfig colors datasetIndices nodesErrors updateErrorsTimer = do
+ -- Ok, web-page was reload (i.e. it's the first update after DOM-rendering),
+ -- so displayed state should be restored immediately.
+ connected <- liftIO $ readTVarIO connectedNodes
+ addColumnsForConnected window connected loggingConfig nodesErrors updateErrorsTimer
+ checkNoNodesState window connected
+ askNSetNodeInfo window dpRequestors connected displayedElements
+ addDatasetsForConnected window connected colors datasetIndices displayedElements
+ liftIO $ updateDisplayedElements displayedElements connected
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Resources.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Resources.hs
new file mode 100644
index 00000000000..97508ea4cd8
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Resources.hs
@@ -0,0 +1,100 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE NumericUnderscores #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Cardano.Tracer.Handlers.RTView.Update.Resources
+ ( updateResourcesHistory
+ ) where
+
+import Control.Concurrent.STM.TVar (readTVarIO)
+import qualified Data.Map.Strict as M
+import Data.Time.Clock
+import Data.Text.Read
+import Data.Word (Word64)
+
+import Cardano.Tracer.Handlers.Metrics.Utils
+import Cardano.Tracer.Handlers.RTView.State.Historical
+import Cardano.Tracer.Handlers.RTView.State.Last
+import Cardano.Tracer.Handlers.RTView.Update.Utils
+import Cardano.Tracer.Types
+
+updateResourcesHistory
+ :: NodeId
+ -> ResourcesHistory
+ -> LastResources
+ -> MetricName
+ -> MetricValue
+ -> UTCTime
+ -> IO ()
+updateResourcesHistory nodeId (ResHistory rHistory) lastResources metricName metricValue now =
+ case metricName of
+ "stat.cputicks" -> updateCPUUsage
+ "mem.resident" -> updateRSSMemory
+ "rts.gcLiveBytes" -> updateGCLiveMemory
+ "rts.gcMajorNum" -> updateGCMajorNum
+ "rts.gcMinorNum" -> updateGCMinorNum
+ "rts.gcticks" -> updateCPUTimeGC
+ "rts.mutticks" -> updateCPUTimeApp
+ "rts.stat.threads" -> updateThreadsNum
+ _ -> return ()
+ where
+ updateCPUUsage =
+ case decimal metricValue of
+ Left _ -> return ()
+ Right (cpuTicks :: Int, _) -> do
+ lastOnes <- readTVarIO lastResources
+ case M.lookup nodeId lastOnes of
+ Nothing ->
+ -- There is no last resources for this node yet.
+ addNullResources lastResources nodeId
+ Just resourcesForNode -> do
+ let tns = utc2ns now
+ tDiffInSec = max 0.1 $ fromIntegral (tns - cpuLastNS resourcesForNode) / 1000_000_000 :: Double
+ ticksDiff = cpuTicks - cpuLastTicks resourcesForNode
+ !cpuV = fromIntegral ticksDiff / fromIntegral (100 :: Int) / tDiffInSec
+ newCPUPct = if cpuV < 0 then 0.0 else cpuV * 100.0
+ addHistoricalData rHistory nodeId now CPUData $ ValueD newCPUPct
+ updateLastResources lastResources nodeId $ \current ->
+ current { cpuLastTicks = cpuTicks
+ , cpuLastNS = tns
+ }
+
+ updateRSSMemory =
+ case decimal metricValue of
+ Left _ -> return ()
+ Right (bytes :: Word64, _) -> do
+ let !memoryInMB = fromIntegral bytes / 1024 / 1024 :: Double
+ addHistoricalData rHistory nodeId now MemoryData $ ValueD memoryInMB
+
+ updateGCLiveMemory =
+ case decimal metricValue of
+ Left _ -> return ()
+ Right (bytes :: Word64, _) -> do
+ let !memoryInMB = fromIntegral bytes / 1024 / 1024 :: Double
+ addHistoricalData rHistory nodeId now GCLiveMemoryData $ ValueD memoryInMB
+
+ updateGCMajorNum =
+ readValueI metricValue $ addHistoricalData rHistory nodeId now GCMajorNumData
+
+ updateGCMinorNum =
+ readValueI metricValue $ addHistoricalData rHistory nodeId now GCMinorNumData
+
+ updateCPUTimeGC =
+ case decimal metricValue of
+ Left _ -> return ()
+ Right (cpuTimeGCInCentiS :: Int, _) -> do
+ -- This is a total CPU time used by the GC, as 1/100 second.
+ let !cpuTimeGCInMs = cpuTimeGCInCentiS * 10
+ addHistoricalData rHistory nodeId now CPUTimeGCData $ ValueI cpuTimeGCInMs
+
+ updateCPUTimeApp =
+ case decimal metricValue of
+ Left _ -> return ()
+ Right (cpuTimeAppInCentiS :: Int, _) -> do
+ -- This is a total CPU time used by the the node itself, as 1/100 second.
+ let !cpuTimeAppInMs = cpuTimeAppInCentiS * 10
+ addHistoricalData rHistory nodeId now CPUTimeAppData $ ValueI cpuTimeAppInMs
+
+ updateThreadsNum =
+ readValueI metricValue $ addHistoricalData rHistory nodeId now ThreadsNumData
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Transactions.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Transactions.hs
new file mode 100644
index 00000000000..a393dc904af
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Transactions.hs
@@ -0,0 +1,41 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Cardano.Tracer.Handlers.RTView.Update.Transactions
+ ( updateTransactionsHistory
+ ) where
+
+import Data.Time.Clock
+import Data.Text.Read
+
+import Cardano.Tracer.Handlers.Metrics.Utils
+import Cardano.Tracer.Handlers.RTView.State.Historical
+import Cardano.Tracer.Types
+
+updateTransactionsHistory
+ :: NodeId
+ -> TransactionsHistory
+ -> MetricName
+ -> MetricValue
+ -> UTCTime
+ -> IO ()
+updateTransactionsHistory nodeId (TXHistory tHistory) metricName metricValue now =
+ case metricName of
+ "cardano.node.txsProcessedNum" -> updateTxsProcessedNum
+ "cardano.node.mempoolBytes" -> updateMempoolBytes
+ "cardano.node.txsInMempool" -> updateTxsInMempool
+ _ -> return ()
+ where
+ updateTxsProcessedNum =
+ readValueI metricValue $ addHistoricalData tHistory nodeId now TxsProcessedNumData
+
+ updateTxsInMempool =
+ readValueI metricValue $ addHistoricalData tHistory nodeId now TxsInMempoolData
+
+ updateMempoolBytes =
+ case decimal metricValue of
+ Left _ -> return ()
+ Right (mempoolBytes :: Int, _) -> do
+ let !mempoolInMB = fromIntegral mempoolBytes / 1024 / 1024 :: Double
+ addHistoricalData tHistory nodeId now MempoolBytesData $ ValueD mempoolInMB
diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Utils.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Utils.hs
new file mode 100644
index 00000000000..f92466eb24f
--- /dev/null
+++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Utils.hs
@@ -0,0 +1,73 @@
+{-# LANGUAGE NumericUnderscores #-}
+
+module Cardano.Tracer.Handlers.RTView.Update.Utils
+ ( askDataPoint
+ , utc2ns
+ , utc2s
+ , s2utc
+ , showT
+ , readInt
+ , nullTime
+ ) where
+
+import Control.Concurrent.STM.TVar (readTVarIO)
+import Data.Aeson (FromJSON, decode')
+import qualified Data.Map.Strict as M
+import Data.Text (Text, pack)
+import Data.Text.Read (decimal)
+import Data.Time.Calendar
+import Data.Time.Clock (UTCTime (..))
+import Data.Time.Clock.POSIX
+import Data.Word (Word64)
+
+import Trace.Forward.Utils.DataPoint (askForDataPoints)
+import Trace.Forward.Protocol.DataPoint.Type (DataPointName)
+
+import Cardano.Tracer.Types
+
+-- | There is a different information the node can provide us by explicit request.
+-- This is a structured data about internal state of the node (for example, its
+-- basic information like version, protocol, commit hash, start time, etc).
+--
+-- Such a structured data is provided as a 'DataPoint'. When it's receved, it's
+-- technically a lazy bytestring that is a result of 'ToJSON'-encoding on the
+-- forwarder's side. Here we can decode it to particular Haskell type provided
+-- by the node.
+askDataPoint
+ :: FromJSON a
+ => DataPointRequestors
+ -> NodeId
+ -> DataPointName
+ -> IO (Maybe a)
+askDataPoint dpRequestors nodeId dpName = do
+ requestors <- readTVarIO dpRequestors
+ case M.lookup nodeId requestors of
+ Nothing -> return Nothing
+ Just dpRequestor -> do
+ dp <- askForDataPoints dpRequestor [dpName]
+ case lookup dpName dp of
+ Just (Just rawValue) -> return $ decode' rawValue
+ _ -> return Nothing
+
+-- | Converts a timestamp to seconds since Unix epoch.
+utc2s :: UTCTime -> Word64
+utc2s utc = fromInteger . round $ utcTimeToPOSIXSeconds utc
+
+-- | Converts a timestamp to nanoseconds since Unix epoch.
+utc2ns :: UTCTime -> Word64
+utc2ns utc = fromInteger . round $ 1000_000_000 * utcTimeToPOSIXSeconds utc
+
+s2utc :: Word64 -> UTCTime
+s2utc posixTime = posixSecondsToUTCTime $ fromIntegral posixTime
+
+showT :: Show a => a -> Text
+showT = pack . show
+
+readInt :: Text -> Int -> Int
+readInt t defInt =
+ case decimal t of
+ Left _ -> defInt
+ Right (i, _) -> i
+
+nullTime :: UTCTime
+nullTime = UTCTime (ModifiedJulianDay 0) 0
diff --git a/cardano-tracer/src/Cardano/Tracer/Run.hs b/cardano-tracer/src/Cardano/Tracer/Run.hs
index e3c3e07b4d5..14d6369aa5b 100644
--- a/cardano-tracer/src/Cardano/Tracer/Run.hs
+++ b/cardano-tracer/src/Cardano/Tracer/Run.hs
@@ -15,30 +15,34 @@ import Cardano.Tracer.CLI (TracerParams (..))
import Cardano.Tracer.Configuration (TracerConfig, readTracerConfig)
import Cardano.Tracer.Handlers.Logs.Rotator (runLogsRotator)
import Cardano.Tracer.Handlers.Metrics.Servers (runMetricsServers)
+import Cardano.Tracer.Handlers.RTView.Run (initSavedTraceObjects, runRTView)
import Cardano.Tracer.Types (DataPointRequestors, ProtocolsBrake)
import Cardano.Tracer.Utils (initAcceptedMetrics, initConnectedNodes,
- initDataPointRequestors, initProtocolsBrake, lift3M)
+ initDataPointRequestors, initProtocolsBrake)
-- | Top-level run function, called by 'cardano-tracer' app.
runCardanoTracer :: TracerParams -> IO ()
-runCardanoTracer TracerParams{tracerConfig} = lift3M
- doRunCardanoTracer (readTracerConfig tracerConfig)
- initProtocolsBrake
- initDataPointRequestors
+runCardanoTracer TracerParams{tracerConfig} = do
+ config <- readTracerConfig tracerConfig
+ brake <- initProtocolsBrake
+ dpRequestors <- initDataPointRequestors
+ doRunCardanoTracer config brake dpRequestors
-- | Runs all internal services of the tracer.
doRunCardanoTracer
- :: TracerConfig -- ^ Tracer's configuration.
- -> ProtocolsBrake -- ^ The flag we use to stop all the protocols.
+ :: TracerConfig -- ^ Tracer's configuration.
+ -> ProtocolsBrake -- ^ The flag we use to stop all the protocols.
-> DataPointRequestors -- ^ The DataPointRequestors to ask 'DataPoint's.
-> IO ()
doRunCardanoTracer config protocolsBrake dpRequestors = do
connectedNodes <- initConnectedNodes
acceptedMetrics <- initAcceptedMetrics
currentLogLock <- newLock
+ savedTO <- initSavedTraceObjects
void . sequenceConcurrently $
[ runLogsRotator config currentLogLock
, runMetricsServers config connectedNodes acceptedMetrics
- , runAcceptors config connectedNodes acceptedMetrics
+ , runAcceptors config connectedNodes acceptedMetrics savedTO
dpRequestors protocolsBrake currentLogLock
+ , runRTView config connectedNodes acceptedMetrics savedTO dpRequestors
]
diff --git a/cardano-tracer/test/Cardano/Tracer/Test/Acceptor.hs b/cardano-tracer/test/Cardano/Tracer/Test/Acceptor.hs
index 859f8b6f243..153dad97d82 100644
--- a/cardano-tracer/test/Cardano/Tracer/Test/Acceptor.hs
+++ b/cardano-tracer/test/Cardano/Tracer/Test/Acceptor.hs
@@ -18,6 +18,7 @@ import System.Time.Extra (sleep)
import Cardano.Tracer.Acceptors.Run (runAcceptors)
import Cardano.Tracer.Configuration
+import Cardano.Tracer.Handlers.RTView.Run (initSavedTraceObjects)
import Cardano.Tracer.Types (DataPointRequestors)
import Cardano.Tracer.Utils (initAcceptedMetrics, initConnectedNodes,
initDataPointRequestors, initProtocolsBrake)
@@ -35,9 +36,10 @@ launchAcceptorsSimple mode localSock dpName = do
dpRequestors <- initDataPointRequestors
connectedNodes <- initConnectedNodes
acceptedMetrics <- initAcceptedMetrics
+ savedTO <- initSavedTraceObjects
currentLogLock <- newLock
void . sequenceConcurrently $
- [ runAcceptors mkConfig connectedNodes acceptedMetrics
+ [ runAcceptors mkConfig connectedNodes acceptedMetrics savedTO
dpRequestors protocolsBrake currentLogLock
, runDataPointsPrinter dpName dpRequestors
]
@@ -51,6 +53,7 @@ launchAcceptorsSimple mode localSock dpName = do
, ekgRequestFreq = Just 1.0
, hasEKG = Nothing
, hasPrometheus = Nothing
+ , hasRTView = Nothing
, logging = NE.fromList [LoggingParams "/tmp/demo-acceptor" FileMode ForHuman]
, rotation = Nothing
, verbosity = Just Minimum
diff --git a/cardano-tracer/test/Cardano/Tracer/Test/DataPoint/Tests.hs b/cardano-tracer/test/Cardano/Tracer/Test/DataPoint/Tests.hs
index edbda9e81b5..a3931fd3b48 100644
--- a/cardano-tracer/test/Cardano/Tracer/Test/DataPoint/Tests.hs
+++ b/cardano-tracer/test/Cardano/Tracer/Test/DataPoint/Tests.hs
@@ -83,6 +83,7 @@ propDataPoint rootDir localSock = do
, ekgRequestFreq = Just 1.0
, hasEKG = Nothing
, hasPrometheus = Nothing
+ , hasRTView = Nothing
, logging = NE.fromList [LoggingParams rootDir FileMode ForHuman]
, rotation = Nothing
, verbosity = Just Minimum
diff --git a/cardano-tracer/test/Cardano/Tracer/Test/Logs/Tests.hs b/cardano-tracer/test/Cardano/Tracer/Test/Logs/Tests.hs
index b98311c19d0..26b8f5c1874 100644
--- a/cardano-tracer/test/Cardano/Tracer/Test/Logs/Tests.hs
+++ b/cardano-tracer/test/Cardano/Tracer/Test/Logs/Tests.hs
@@ -67,6 +67,7 @@ propLogs format rootDir localSock = do
, ekgRequestFreq = Just 1.0
, hasEKG = Nothing
, hasPrometheus = Nothing
+ , hasRTView = Nothing
, logging = NE.fromList [LoggingParams root FileMode format]
, rotation = Just $ RotationParams
{ rpFrequencySecs = 3
@@ -98,6 +99,7 @@ propMultiInit format rootDir localSock1 localSock2 = do
, ekgRequestFreq = Just 1.0
, hasEKG = Nothing
, hasPrometheus = Nothing
+ , hasRTView = Nothing
, logging = NE.fromList [LoggingParams root FileMode format]
, rotation = Nothing
, verbosity = Just Minimum
@@ -124,6 +126,7 @@ propMultiResp format rootDir localSock = do
, ekgRequestFreq = Just 1.0
, hasEKG = Nothing
, hasPrometheus = Nothing
+ , hasRTView = Nothing
, logging = NE.fromList [LoggingParams root FileMode format]
, rotation = Nothing
, verbosity = Just Minimum
diff --git a/cardano-tracer/test/Cardano/Tracer/Test/Restart/Tests.hs b/cardano-tracer/test/Cardano/Tracer/Test/Restart/Tests.hs
index a96db2f2fbf..0f175773f31 100644
--- a/cardano-tracer/test/Cardano/Tracer/Test/Restart/Tests.hs
+++ b/cardano-tracer/test/Cardano/Tracer/Test/Restart/Tests.hs
@@ -28,12 +28,13 @@ tests = localOption (QuickCheckTests 1) $ testGroup "Test.Restart"
]
propNetworkForwarder :: FilePath -> FilePath -> IO Property
-propNetworkForwarder rootDir localSock =
+propNetworkForwarder rootDir localSock = do
+ let config = mkConfig rootDir localSock
+ brake <- initProtocolsBrake
+ dpRequestors <- initDataPointRequestors
propNetwork' rootDir
( launchForwardersSimple Initiator localSock 1000 10000
- , lift3M doRunCardanoTracer (return $ mkConfig rootDir localSock)
- initProtocolsBrake
- initDataPointRequestors
+ , doRunCardanoTracer config brake dpRequestors
)
propNetwork'
@@ -82,6 +83,7 @@ mkConfig root p = TracerConfig
, ekgRequestFreq = Just 1.0
, hasEKG = Nothing
, hasPrometheus = Nothing
+ , hasRTView = Nothing
, logging = NE.fromList [LoggingParams root FileMode ForMachine]
, rotation = Nothing
, verbosity = Just Minimum
diff --git a/nix/nixos/cardano-tracer-service.nix b/nix/nixos/cardano-tracer-service.nix
index e853161cfdf..5eb97c3bc97 100644
--- a/nix/nixos/cardano-tracer-service.nix
+++ b/nix/nixos/cardano-tracer-service.nix
@@ -2,7 +2,7 @@ pkgs:
let serviceConfigToJSON =
cfg:
{
- networkMagic = 764824073; ## Mainnet
+ inherit (cfg) networkMagic;
# loRequestNum = 100;
network =
if cfg.acceptingSocket != null
@@ -28,6 +28,10 @@ let serviceConfigToJSON =
rpMaxAgeHours = 24;
};
+ hasRTView = {
+ epHost = "127.0.0.1";
+ epPort = 3300;
+ };
hasEKG = [
{ epHost = "127.0.0.1";
epPort = 3100; ## supervisord.portShiftPrometheus
@@ -58,9 +62,10 @@ in pkgs.commonLib.defServiceModule
extraOptionDecls = {
### You can actually change those!
- acceptingSocket = mayOpt str "Socket path: as acceptor.";
- connectToSocket = mayOpt str "Socket path: connect to.";
- logRoot = opt str null "Log storage root directory.";
+ networkMagic = opt int 764824073 "Network magic (764824073 for Cardano mainnet).";
+ acceptingSocket = mayOpt str "Socket path: as acceptor.";
+ connectToSocket = mayOpt str "Socket path: connect to.";
+ logRoot = opt str null "Log storage root directory.";
### Here be dragons, on the other hand..
configFile = mayOpt str
diff --git a/nix/workbench/backend.sh b/nix/workbench/backend.sh
index 2250728a0b2..9f7dabfde07 100644
--- a/nix/workbench/backend.sh
+++ b/nix/workbench/backend.sh
@@ -10,8 +10,8 @@ usage_backend() {
allocate-run RUNDIR
describe-run RUNDIR
- start-cluster RUNDIR
- Start the cluster nodes
+ start RUNDIR Start the backend
+ start-nodes RUNDIR
start-node RUNDIR NODE-NAME
stop-node RUNDIR NODE-NAME
wait-node RUNDIR NODE-NAME
@@ -43,7 +43,8 @@ case "${op}" in
setenv-defaults ) backend_$WORKBENCH_BACKEND "$@";;
allocate-run ) backend_$WORKBENCH_BACKEND "$@";;
describe-run ) backend_$WORKBENCH_BACKEND "$@";;
- start-cluster ) backend_$WORKBENCH_BACKEND "$@";;
+ start ) backend_$WORKBENCH_BACKEND "$@";;
+ start-nodes ) backend_$WORKBENCH_BACKEND "$@";;
start-node ) backend_$WORKBENCH_BACKEND "$@";;
stop-node ) backend_$WORKBENCH_BACKEND "$@";;
wait-node ) backend_$WORKBENCH_BACKEND "$@";;
diff --git a/nix/workbench/lib-cabal.sh b/nix/workbench/lib-cabal.sh
index 3d903e15b22..530a3e6ba45 100644
--- a/nix/workbench/lib-cabal.sh
+++ b/nix/workbench/lib-cabal.sh
@@ -10,7 +10,7 @@ function workbench-prebuild-executables()
echo "workbench: prebuilding executables (because of useCabalRun)"
unset NIX_ENFORCE_PURITY
- for exe in cardano-node cardano-cli cardano-topology tx-generator locli
+ for exe in cardano-node cardano-cli cardano-topology cardano-tracer tx-generator locli
do echo "workbench: $(with_color blue prebuilding) $(with_color red $exe)"
cabal -v0 build -- exe:$exe 2>&1 >/dev/null |
{ grep -v 'exprType TYPE'; true; } || return 1
@@ -30,6 +30,10 @@ function cardano-topology() {
cabal -v0 run exe:cardano-topology -- "$@"
}
+function cardano-tracer() {
+ cabal -v0 run exe:cardano-tracer -- "$@"
+}
+
function locli() {
cabal -v0 build exe:locli
set-git-rev \
@@ -44,4 +48,4 @@ function tx-generator() {
export WORKBENCH_CABAL_MODE=t
-export -f cardano-cli cardano-node cardano-topology locli tx-generator
+export -f cardano-cli cardano-node cardano-topology cardano-tracer locli tx-generator
diff --git a/nix/workbench/profile.sh b/nix/workbench/profile.sh
index a2fc46b3dcc..c84da3f729c 100644
--- a/nix/workbench/profile.sh
+++ b/nix/workbench/profile.sh
@@ -49,6 +49,7 @@ case "$op" in
' --exit-status --arg name "$name" >/dev/null
;;
+ ## XXX: does not respect overlays!!
compose )
local profile_names="$@"
@@ -64,10 +65,17 @@ case "$op" in
local usage="USAGE: wb profile $op [NAME=