homepage: new homepage to replace cockpit
M .hgignore +2 -0
@@ 7,3 7,5 @@ dist
 data/pgdb
 elm-stuff
 node
+# Default ignored files
+.idea/workspace.xml

          
M Makefile +5 -1
@@ 4,7 4,10 @@ NODE_PATH ?= /usr/lib/node_modules
 ACE_TAR ?= v1.4.11.tar.gz
 ACE_URL ?= https://github.com/ajaxorg/ace-builds/archive
 
-all: delete info groupinfo search plot cache qeditor tseditor formula pygmentize
+all: homepage delete info groupinfo search plot cache qeditor tseditor formula pygmentize
+
+homepage:
+	elm make elm/Homepage.elm $(FLAGS) --output tsview/tsview_static/homepage_elm.js
 
 delete:
 	elm make elm/Delete.elm $(FLAGS) --output tsview/tsview_static/delete_elm.js

          
@@ 60,6 63,7 @@ cleanstuff:
 	rm elm-stuff -rf
 
 cleanbuild:
+	rm tsview/tsview_static/homepage_elm.js -f
 	rm tsview/tsview_static/delete_elm.js -f
 	rm tsview/tsview_static/plot_elm.js -f
 	rm tsview/tsview_static/info_elm.js -f

          
A => elm/Homepage.elm +334 -0
@@ 0,0 1,334 @@ 
+module Homepage exposing (main)
+
+import Browser
+import Catalog as Cat exposing (Msg(..))
+import Dict exposing (Dict)
+import Html as H
+import Html.Attributes as HA
+import Menu as Men exposing (iconesDefinition)
+import Set exposing (Set)
+import Svg exposing (path, svg)
+import Svg.Attributes
+    exposing
+        ( d
+        , fill
+        , fillRule
+        , viewBox
+        )
+
+
+type Status
+    = Processing
+
+
+type alias MultiStatus =
+    { catalog : Status }
+
+
+type alias Model =
+    { baseUrl : String
+    , instance : String
+    , version : String
+    , status : MultiStatus
+    , catalog : Cat.Model
+    , menu : Men.Model
+    }
+
+
+type Msg
+    = GotCatalog Cat.Msg
+    | Menu Men.Msg
+
+
+nocmd model =
+    ( model, Cmd.none )
+
+
+update : Msg -> Model -> ( Model, Cmd Msg )
+update msg model =
+    case msg of
+        GotCatalog catmsg ->
+            nocmd { model | catalog = Cat.update catmsg model.catalog }
+
+        Menu menumsg ->
+            nocmd { model | menu = Men.updateModel menumsg model.menu }
+
+
+buildSvg icones iconeName =
+    let
+        icone =
+            Maybe.withDefault
+                []
+                (Dict.get iconeName icones)
+    in
+    svg
+        [ viewBox "0 0 16 16"
+        , fill "currentColor"
+        ]
+        (List.map
+            (\ipath -> buildSvgPath ipath)
+            icone
+        )
+
+
+buildSvgPath ipath =
+    case ipath.fillRule of
+        Nothing ->
+            path [ d ipath.d ] []
+
+        Just rule ->
+            path [ fillRule rule, d ipath.d ] []
+
+
+buildDetailsDiv : Model -> H.Html Msg
+buildDetailsDiv model =
+    H.div
+        [ HA.class "details" ]
+        [ H.h1
+            []
+            [ H.u
+                []
+                [ H.text model.instance ]
+            , H.text " refinery"
+            ]
+        , H.img [ HA.src ("https://img.shields.io/badge/refinery-" ++ model.version ++ "-35845F") ] []
+        , H.br [] []
+        , H.p [] [ H.text "From here, you can access :" ]
+        , H.ul []
+            [ H.p [] [ H.text (String.fromInt (List.length model.catalog.series) ++ " Series") ]
+            , H.p [] [ H.text (" ↳ " ++ (String.fromInt (List.length (Set.toList (Maybe.withDefault Set.empty (Dict.get "primary" model.catalog.seriesbykind)))) ++ " Primaries")) ]
+            , H.p [] [ H.text (" ↳ " ++ (String.fromInt (List.length (Set.toList (Maybe.withDefault Set.empty (Dict.get "formula" model.catalog.seriesbykind)))) ++ " Formulas")) ]
+            ]
+        , H.br [] []
+        , H.p [ HA.hidden (Dict.keys model.catalog.seriesbysource == []) ] [ H.text "Sources :" ]
+        , H.ul []
+            (List.map
+                (\elt -> H.p [] [ H.text elt ])
+                (Dict.keys model.catalog.seriesbysource)
+            )
+        ]
+
+
+buildLinksDiv : Model -> H.Html Msg
+buildLinksDiv model =
+    H.div
+        [ HA.id "links" ]
+        [ H.a
+            [ HA.class "button"
+            , HA.class "apibutton"
+            , HA.target "_blank"
+            , HA.href (model.baseUrl ++ "/api") ]
+            [ H.img [ HA.src "./tsview_static/arrow_outward.png" ] []
+            , H.text "API" ]
+        , H.br [] []
+        , H.a
+            [ HA.class "button"
+            , HA.class "greenbutton"
+            , HA.target "_blank"
+            , HA.href "https://tshistory-refinery.readthedocs.io/en/latest/" ]
+            [ H.text "documentation" ]
+        , H.br [] []
+        , H.a
+            [ HA.class "button"
+            , HA.class "redbutton"
+            , HA.target "_blank"
+            , HA.href "mailto: contact@pythonian.fr" ]
+            [ H.text "Contact Pythonian" ]
+        ]
+
+
+buildGetStartedDiv : Model -> H.Html Msg
+buildGetStartedDiv model =
+    H.div
+        [ HA.id "getstarted" ]
+        [ H.p
+            [ HA.class "greentext" ]
+            [ H.text "The power of the Timeseries Refinery" ]
+        , H.h2 [] [ H.text "Get Started" ]
+        , H.div
+            [ HA.class "flex-container-center" ]
+            [ H.div [ HA.class "card" ]
+                [ H.div
+                    [ HA.class "card-img-top" ]
+                    [ buildSvg iconesDefinition "bi bi-download" ]
+                , H.br [] []
+                , H.div
+                    [ HA.class "card-body" ]
+                    [ H.h4 [ HA.class "card-title" ] [ H.text "Import data" ]
+                    , H.br [] []
+                    , H.p
+                        [ HA.class "card-text" ]
+                        [ H.text
+                            "Data can be imported through the "
+                        , H.a [ HA.href (model.baseUrl ++ "/api") ] [ H.text "API." ]
+                        ]
+                    , H.p
+                        [ HA.class "card-text" ]
+                        [ H.text
+                            "To facilitate imports, a python client is available as well as an excel client plugin. "
+                        , H.a [ HA.href "mailto: contact@pythonian.fr" ] [ H.text "Contact Pythonian" ]
+                        , H.text
+                            " to have access to tutorials."
+                        ]
+                    , H.p
+                        [ HA.class "card-text" ]
+                        [ H.text
+                            "If tasks have been constructed to import data (scraping open data on the internet, csv files to be ingested…), go to the "
+                        , H.a [ HA.href (model.baseUrl ++ "/tasks") ] [ H.text "task launcher." ]
+                        ]
+                    ]
+                ]
+            , H.div [ HA.class "card" ]
+                [ H.div
+                    [ HA.class "card-img-top" ]
+                    [ buildSvg iconesDefinition "bi bi-speedometer" ]
+                , H.br [] []
+                , H.div
+                    [ HA.class "card-body" ]
+                    [ H.h4 [ HA.class "card-title" ] [ H.text "Get to know your data" ]
+                    , H.br [] []
+                    , H.p [ HA.class "card-text" ]
+                        [ H.text
+                            "To have the complete list of the data available in this refinery, go to the "
+                        , H.a [ HA.href (model.baseUrl ++ "/tssearch") ] [ H.text "catalogue." ]
+                        ]
+                    , H.p [ HA.class "card-text" ]
+                        [ H.text
+                            "From here, there is a redirection for each series to its own information page. There, every interesting information will be found (tzawareness, metadata, last updates, version history… and obviously… a plot!)."
+                        ]
+                    , H.p [ HA.class "card-text" ]
+                        [ H.a [ HA.href (model.baseUrl ++ "/tsview") ] [ H.text "Quick view " ]
+                        , H.text
+                            " will be useful to compare several series on a plot and share the permalinks with colleagues."
+                        ]
+                    ]
+                ]
+            , H.div [ HA.class "card" ]
+                [ H.div
+                    [ HA.class "card-img-top" ]
+                    [ buildSvg iconesDefinition "bi bi-tools" ]
+                , H.br [] []
+                , H.div
+                    [ HA.class "card-body" ]
+                    [ H.h4 [ HA.class "card-title" ] [ H.text "Correct and transform" ]
+                    , H.br [] []
+                    , H.p [ HA.class "card-text" ]
+                        [ H.text
+                            "An outlier has been spotted ? A manual (and versioned!) correction is possible from the info page of the series (go to « edit values »)."
+                        ]
+                    , H.p [ HA.class "card-text" ]
+                        [ H.text
+                            "For series transformation, welcome to the world of formulas ! Operator documentation is available "
+                        , H.a [ HA.href (model.baseUrl ++ "/tsformula/operators") ] [ H.text " here. " ]
+                        , H.text
+                            "The "
+                        , H.a [ HA.href (model.baseUrl ++ "/tsformula") ] [ H.text "formula editor" ]
+                        , H.text
+                            " is useful to construct one formula. If a batch of formulas has to be pushed, prefer the "
+                        , H.a [ HA.href (model.baseUrl ++ "/addformulas") ] [ H.text "csv solution." ]
+                        ]
+                    , H.p [ HA.class "card-text" ]
+                        [ H.text
+                            "If a monster formulas has been constructed (with hundreds of dependencies…), don’t hesitate to setup a "
+                        , H.a [ HA.href (model.baseUrl ++ "/formulacache") ] [ H.text "cache policy!" ]
+                        ]
+                    ]
+                ]
+            , H.div [ HA.class "card" ]
+                [ H.div
+                    [ HA.class "card-img-top" ]
+                    [ buildSvg iconesDefinition "bi bi-heart-pulse" ]
+                , H.br [] []
+                , H.div
+                    [ HA.class "card-body" ]
+                    [ H.h4 [ HA.class "card-title" ] [ H.text "Monitor refinery health" ]
+                    , H.br [] []
+                    , H.p [ HA.class "card-text" ]
+                        [ H.text
+                            "Is everything ok with this refinery ?"
+                        ]
+                    , H.p [ HA.class "card-text" ]
+                        [ H.text
+                                "Go to "
+                        , H.a [ HA.href (model.baseUrl ++ "/tswatch") ] [ H.text "tswatch " ]
+                        , H.text
+                                " to check if the data is correctly updated."
+                        ]
+                    , H.p [ HA.class "card-text" ]
+                        [ H.a [ HA.href (model.baseUrl ++ "/tasks") ] [ H.text "Task " ]
+                        , H.text
+                                "page will be also useful to check if all the recent tasks are «done» (tips: it is possible to filter the «status» column)."
+                        ]
+                    ]
+                ]
+            ]
+        ]
+
+
+view : Model -> H.Html Msg
+view model =
+    H.div
+        [ HA.class
+            (if model.menu.menuModeText then
+                "grid-container-text"
+
+             else
+                "grid-container-icon"
+            )
+        ]
+        [ Men.viewMenu model.menu Menu
+        , H.div
+            [ HA.class "main-content"
+            , HA.style "margin" ".5em"
+            ]
+            [ H.div []
+                [ H.div
+                    [ HA.id "home" ]
+                    [ H.div
+                        [ HA.class "flex-container timeseriesrefinery home-container" ]
+                        [ buildDetailsDiv model ]
+                    , H.img [ HA.class "home-image", HA.src "./tsview_static/grid.png" ] []
+                    , buildLinksDiv model
+                    ]
+                , buildGetStartedDiv model
+                , H.div
+                    [ HA.id "footer" ]
+                    [ H.img [ HA.class "pythonian-logo", HA.src "./tsview_static/logo-pythonian.png" ] [] ]
+                ]
+            ]
+        ]
+
+
+subscriptions : Model -> Sub Msg
+subscriptions model =
+    Sub.none
+
+
+initModel baseurl instance version =
+    { baseUrl = baseurl
+    , instance = instance
+    , version = version
+    , status = { catalog = Processing }
+    , catalog = Cat.empty
+    , menu =
+        { menuContent = Men.contentMenu
+        , menuModeText = False
+        , icones = Men.iconesDefinition
+        }
+    }
+
+
+init : ( String, String, String ) -> ( Model, Cmd Msg )
+init ( baseurl, instance, version ) =
+    ( initModel baseurl instance version
+    , Cmd.map GotCatalog <| Cat.get baseurl "series" 1 Cat.ReceivedSeries
+    )
+
+
+main =
+    Browser.element
+        { init = init
+        , update = update
+        , subscriptions = subscriptions
+        , view = view
+        }

          
M elm/Menu.elm +1 -1
@@ 45,7 45,7 @@ iconesDefinition =
         , ("bi bi-exclamation-triangle", [Path "M7.938 2.016A.13.13 0 0 1 8.002 2a.13.13 0 0 1 .063.016.15.15 0 0 1 .054.057l6.857 11.667c.036.06.035.124.002.183a.2.2 0 0 1-.054.06.1.1 0 0 1-.066.017H1.146a.1.1 0 0 1-.066-.017.2.2 0 0 1-.054-.06.18.18 0 0 1 .002-.183L7.884 2.073a.15.15 0 0 1 .054-.057m1.044-.45a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767z" Nothing
                                           , Path "M7.002 12a1 1 0 1 1 2 0 1 1 0 0 1-2 0M7.1 5.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0z" Nothing])
         , ("bi bi-code", [Path "M5.854 4.854a.5.5 0 1 0-.708-.708l-3.5 3.5a.5.5 0 0 0 0 .708l3.5 3.5a.5.5 0 0 0 .708-.708L2.707 8zm4.292 0a.5.5 0 0 1 .708-.708l3.5 3.5a.5.5 0 0 1 0 .708l-3.5 3.5a.5.5 0 0 1-.708-.708L13.293 8z" Nothing])
-        , ("bi bi-dowload", [Path "M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5" Nothing
+        , ("bi bi-download", [Path "M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5" Nothing
                            , Path "M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708z" Nothing])
         , ("bi bi-speedometer", [Path "M8 2a.5.5 0 0 1 .5.5V4a.5.5 0 0 1-1 0V2.5A.5.5 0 0 1 8 2M3.732 3.732a.5.5 0 0 1 .707 0l.915.914a.5.5 0 1 1-.708.708l-.914-.915a.5.5 0 0 1 0-.707M2 8a.5.5 0 0 1 .5-.5h1.586a.5.5 0 0 1 0 1H2.5A.5.5 0 0 1 2 8m9.5 0a.5.5 0 0 1 .5-.5h1.5a.5.5 0 0 1 0 1H12a.5.5 0 0 1-.5-.5m.754-4.246a.39.39 0 0 0-.527-.02L7.547 7.31A.91.91 0 1 0 8.85 8.569l3.434-4.297a.39.39 0 0 0-.029-.518z" Nothing
                                 ,Path "M6.664 15.889A8 8 0 1 1 9.336.11a8 8 0 0 1-2.672 15.78zm-4.665-4.283A11.95 11.95 0 0 1 8 10c2.186 0 4.236.585 6.001 1.606a7 7 0 1 0-12.002 0" Nothing])

          
M tsview/blueprint.py +14 -0
@@ 11,6 11,8 @@ from tshistory import search
 from tshistory_formula.registry import FUNCS
 from tshistory_formula.interpreter import jsontypes
 
+import tshistory_refinery
+
 from tsview.util import (
     argsdict as _argsdict,
     format_formula

          
@@ 56,6 58,18 @@ def tsview(tsa):
         static_folder='tsview_static',
     )
 
+    @bp.route('/')
+    def homepage():
+        if not has_roles('admin', 'rw', 'ro'):
+            return 'Nothing to see there.'
+        baseurl = homeurl()
+        instance = "Local" if len(baseurl)==0 else baseurl.split("refinery.")[1].split(".pythonian")[0]
+        version = tshistory_refinery.__version__
+        return render_template(
+            'homepage.html',
+            flags=json.dumps([baseurl, instance, version]),
+        )
+
     @bp.route('/tsview')
     def home():
         if not has_roles('admin', 'rw', 'ro'):

          
A => tsview/tsview_static/arrow_outward.png +0 -0

A => tsview/tsview_static/grid.png +0 -0

A => tsview/tsview_static/homepage_elm.css +250 -0
@@ 0,0 1,250 @@ 
+@import url("https://fonts.googleapis.com/css?family=Inter:400,500|Ubuntu:400,500|Open+Sans:400,700");
+
+html {
+    scroll-behavior: smooth;
+}
+
+body {
+    font-size: 14px;
+}
+
+p {
+    font-family: 'Open Sans';
+    font-style: normal;
+    font-weight: 400;
+    font-size: 16px;
+    line-height: 24px;
+    text-align: center;
+    color: #515350;
+}
+
+h1 {
+    font-family: 'Ubuntu';
+    font-style: normal;
+    font-weight: 500;
+    font-size: 56px;
+    line-height: 72px;
+    letter-spacing: -0.02em;
+    color: #1A1C1A;
+}
+
+h2 {
+    font-family: 'Ubuntu';
+    font-style: normal;
+    font-weight: 400;
+    font-size: 48px;
+    line-height: 60px;
+    text-align: center;
+    letter-spacing: -0.02em;
+    color: #1A1C1A;
+    margin-bottom: 48px;
+}
+
+#home {
+    padding: 0px 0px 60px 0px;
+    padding-left: calc((100% - 1280px)/2);
+    padding-right: calc((100% - 1280px)/2);
+}
+
+.flex-container {
+    display: flex;
+    flex-wrap: wrap;
+}
+  
+.home-container {
+    margin-top: 32px;
+    display: inline-block;
+}
+
+.timeseriesrefinery {
+    padding-left: 48px;
+}
+
+.details {
+    width: 670px;
+}
+
+.details p {
+    text-align: left;
+    margin-bottom: 0px !important;
+} 
+
+.details u {
+    text-decoration-color: #35845F;
+}
+
+.details u:hover {
+    color: #35845F !important;
+}
+
+.details ul {
+    padding-inline-start: 12px;
+}
+
+.home-image {
+    height: 400px;
+    position: absolute;
+}
+
+#links {
+    position: absolute;
+    margin-top: 32px;
+    display: inline-flex;
+}
+
+a.button {
+    display: inline-block;
+    padding: 0.65rem 0.75rem;
+    margin: 6px;
+    text-align: center;
+    white-space: nowrap;
+    vertical-align: middle;
+    height: 56px;
+    box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
+    border-radius: 8px;
+    font-family: 'Ubuntu';
+    font-style: normal;
+    font-weight: 300;
+    font-size: 18px;
+    line-height: 28px;
+}
+
+a.button:hover {
+    text-decoration: none;
+}
+
+a.apibutton {
+    width: 154px;
+    background: #FFFFFF;
+    border: 1px solid #D0D5DD;
+    color: #344054 !important;
+}
+  
+a.apibutton:hover {
+    background-color: #F0F0F0 !important;
+    border: 1px solid #D0D5DD;
+}
+
+.apibutton img {
+    height: 24px;
+    margin: 0px 10px;
+}
+
+a.greenbutton {
+    width: 180px;
+    background: #35845F;
+    border: 1px solid #35845F;
+    color: white !important;
+}
+  
+a.greenbutton:hover {
+    background-color: #275D44 !important;
+    border: 1px solid #275D44;
+}
+  
+a.redbutton {
+    width: 180px;
+    background: #ab2d25;
+    border: 1px solid #ab2d25;
+    color: white !important;
+}
+    
+a.redbutton:hover {
+    background-color: #3b0605 !important;
+    border: 1px solid #3b0605;
+}
+
+#getstarted {
+    background-color: #F8F8F8;
+    padding: 32px 0px ;
+    padding-left: calc((100% - 1280px)/2);
+    padding-right: calc((100% - 1280px)/2);
+}
+
+.greentext {
+    font-family: 'Ubuntu';
+    font-style: normal;
+    font-weight: 400;
+    font-size: 20px;
+    line-height: 23px;
+    text-align: center;
+    letter-spacing: -0.02em;
+    color: #35845F;
+    margin-bottom: 8px;
+}
+
+.flex-container-center {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: center;
+}
+
+.card {
+    width: 400px !important;
+    background: #ffffff;
+    border: 1px solid #C5C7C3;
+    padding: 32px;
+    margin: 4px;
+    display: flex;
+    flex-wrap: wrap;
+    text-align: center;
+    align-items: center;
+}
+  
+.card-body{
+    padding: 0px !important;
+}
+  
+.card-img-top{
+    width: 56px;
+    height: 56px;
+}
+
+.pythonian-logo {
+    width: 150px;
+}
+
+#footer {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: center;
+}
+
+@media (max-width: 1294px) and (min-width: 991px) {
+    #links {
+        display: inline;
+    }
+}
+
+@media (max-width: 991px) {
+    .home-image {
+        display: none;
+    }
+
+    #links {
+        display: none;
+    }
+}
+
+@media (max-width: 720px) {
+    .details {
+        max-width: 100%;
+    }
+
+    .flex-container {
+        max-width: -moz-available;
+        max-width: -webkit-fill-available;
+    }
+
+    .home-container {
+        margin-top:32px;
+    }
+
+    #home {
+        padding: 0px 0px 40px 0px;
+    }
+
+    #getstarted {
+        padding: 32px 0px;
+    }
+}
  No newline at end of file

          
A => tsview/tsview_static/logo-pythonian.png +0 -0

A => tsview/tsview_templates/homepage.html +25 -0
@@ 0,0 1,25 @@ 
+<!doctype html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+        <link rel="stylesheet"
+              href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css"
+              integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS"
+              crossorigin="anonymous">
+        <link rel="stylesheet" href="{{ url_for('tsview.static', filename='pygmentize.css') }}"/>
+        <link rel="stylesheet" href="{{ url_for('tsview.static', filename='style.css') }}"/>
+        <link href="{{ url_for('tsview.static', filename='homepage_elm.css')}}" rel="stylesheet"/>
+        <link href="{{ url_for('tsview.static', filename='menu_elm.css')}}" rel="stylesheet"/>
+    </head>
+    <body>
+        <div id="homepage"></div>
+        <script src="./tsview_static/homepage_elm.js"></script>
+        <script>
+          var app = Elm.Homepage.init({
+              node: document.getElementById('homepage'),
+              flags: {{ flags | safe }}
+          });
+        </script>
+    </body>
+</html>