diff --git a/webapp/sources/rudder/rudder-web/src/main/elm/editor/sources/Editor.elm b/webapp/sources/rudder/rudder-web/src/main/elm/editor/sources/Editor.elm index 93d69bfc72b..aa485f43f83 100755 --- a/webapp/sources/rudder/rudder-web/src/main/elm/editor/sources/Editor.elm +++ b/webapp/sources/rudder/rudder-web/src/main/elm/editor/sources/Editor.elm @@ -307,30 +307,38 @@ update msg model = (newMode, idToClean) = case model.mode of TechniqueDetails _ (Edit _) ui -> - (TechniqueDetails technique (Edit technique) ui, technique.id) + (TechniqueDetails technique (Edit technique) {ui | saving = False}, technique.id) TechniqueDetails _ (Creation id) ui -> - (TechniqueDetails technique (Edit technique) ui, id) + (TechniqueDetails technique (Edit technique) {ui | saving = False}, id) TechniqueDetails _ (Clone _ id) ui -> - (TechniqueDetails technique (Edit technique) ui, id) + (TechniqueDetails technique (Edit technique) {ui | saving = False}, id) m -> (m, technique.id) drafts = Dict.remove idToClean.value model.drafts in ({ model | techniques = techniques, mode = newMode, drafts = drafts}, Cmd.batch [ clearDraft idToClean.value, successNotification "Technique saved!", pushUrl technique.id.value] ) SaveTechnique (Err err) -> - ( model , errorNotification ("Error when saving technique: " ++ debugHttpErr err ) ) + let + newModel = case model.mode of + TechniqueDetails t s ui -> {model | mode = TechniqueDetails t s {ui | saving = False}} + _ -> model + in + ( newModel , errorNotification ("Error when saving technique: " ++ debugHttpErr err ) ) StartSaving -> - case model.mode of - TechniqueDetails t o ui -> + case model.mode of + TechniqueDetails t o ui -> + let + newUi = {ui | saving = True} + in case o of Edit _ -> - update (CallApi (saveTechnique t False Nothing)) { model | mode = TechniqueDetails t o ui } + update (CallApi (saveTechnique t False Nothing)) { model | mode = TechniqueDetails t o newUi } Creation internalId -> - update (CallApi (saveTechnique t True (Just internalId))) { model | mode = TechniqueDetails t o ui } + update (CallApi (saveTechnique t True (Just internalId))) { model | mode = TechniqueDetails t o newUi } Clone _ internalId -> - update (CallApi (saveTechnique t True (Just internalId))) { model | mode = TechniqueDetails t o ui } - _ -> (model, Cmd.none) + update (CallApi (saveTechnique t True (Just internalId))) { model | mode = TechniqueDetails t o newUi } + _ -> (model, Cmd.none) DeleteTechnique (Ok (metadata, techniqueId)) -> case model.mode of diff --git a/webapp/sources/rudder/rudder-web/src/main/elm/editor/sources/ViewTechnique.elm b/webapp/sources/rudder/rudder-web/src/main/elm/editor/sources/ViewTechnique.elm index b591097f06d..dfa56b554bf 100755 --- a/webapp/sources/rudder/rudder-web/src/main/elm/editor/sources/ViewTechnique.elm +++ b/webapp/sources/rudder/rudder-web/src/main/elm/editor/sources/ViewTechnique.elm @@ -262,7 +262,13 @@ showTechnique model technique origin ui = elem :: base ) technique.elems ) - + btnSave : Bool -> Bool -> Msg -> Html Msg + btnSave saving disable action = + let + icon = if saving then i [ class "fa fa-spinner fa-pulse"] [] else i [ class "fa fa-download"] [] + in + button [class ("btn btn-success btn-save" ++ (if saving then " saving" else "")), type_ "button", disabled (saving || disable), onClick action] + [ icon ] in div [ class "main-container" ] [ div [ class "main-header" ] [ @@ -280,10 +286,7 @@ showTechnique model technique origin ui = text "Reset " , i [ class "fa fa-undo"] [] ] - , button [ class "btn btn-success btn-save", disabled (isUnchanged || not (isValid ui) || ui.saving || String.isEmpty technique.name|| not areErrorOnMethodParameters || not areErrorOnMethodCondition || not areBlockOnError), onClick StartSaving] [ - text "Save " - , i [ class ("fa fa-download " ++ (if ui.saving then "glyphicon glyphicon-cog fa-spin" else "")) ] [] - ] + , btnSave ui.saving (isUnchanged || not (isValid ui) || String.isEmpty technique.name|| not areErrorOnMethodParameters || not areErrorOnMethodCondition || not areBlockOnError) StartSaving ] ] ] diff --git a/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/DataTypes.elm b/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/DataTypes.elm index b6a3d9a53ea..22780a8c56c 100644 --- a/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/DataTypes.elm +++ b/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/DataTypes.elm @@ -273,6 +273,7 @@ type alias UI = , hasWriteRights : Bool , loadingRules : Bool , isAllCatFold : Bool + , saving : Bool } type alias Changes = @@ -310,7 +311,7 @@ type Msg | NewRule RuleId | NewCategory String | GetRepairedReport RuleId Int - | CallApi (Model -> Cmd Msg) + | CallApi Bool (Model -> Cmd Msg) | GetRuleDetailsResult (Result Error Rule) | GetPolicyModeResult (Result Error String) | GetCategoryDetailsResult (Result Error (Category Rule)) diff --git a/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/Init.elm b/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/Init.elm index 92786c5202e..0e22232da2b 100644 --- a/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/Init.elm +++ b/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/Init.elm @@ -11,7 +11,7 @@ init flags = initCategory = Category "" "" "" (SubCategories []) [] initFilters = Filters (TableFilters Name Asc "" []) (TreeFilters "" [] (Tag "" "") []) - initUI = UI initFilters initFilters initFilters NoModal flags.hasWriteRights True False + initUI = UI initFilters initFilters initFilters NoModal flags.hasWriteRights True False False initModel = Model flags.contextPath Loading "" initCategory initCategory initCategory Dict.empty Dict.empty Dict.empty Dict.empty initUI listInitActions = diff --git a/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/Rules.elm b/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/Rules.elm index 1385b397daf..a69484cf578 100644 --- a/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/Rules.elm +++ b/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/Rules.elm @@ -57,8 +57,12 @@ update msg model = GenerateId nextMsg -> (model, Random.generate nextMsg generator) -- Do an API call - CallApi call -> - (model, call model) + CallApi saving call -> + let + ui = model.ui + newModel = {model | ui = {ui | saving = saving}} + in + (newModel, call model) -- neutral element Ignore -> ( model , Cmd.none) @@ -291,7 +295,8 @@ update msg model = Just _ -> "saved" Nothing -> "created" ui = details.ui - newModel = {model | mode = RuleForm {details | originRule = Just ruleDetails, rule = ruleDetails, ui = {ui | editDirectives = False, editGroups = False }}} + modelUi = model.ui + newModel = {model | mode = RuleForm {details | originRule = Just ruleDetails, rule = ruleDetails, ui = {ui | editDirectives = False, editGroups = False}}, ui = {modelUi | saving = False} } in (newModel, Cmd.batch [ successNotification ("Rule '"++ ruleDetails.name ++"' successfully " ++ action) @@ -515,7 +520,7 @@ processApiError apiName err model = errorMessage in - ({model | mode = if model.mode == Loading then RuleTable else model.mode, ui = { modelUi | loadingRules = False}}, errorNotification ("Error when "++apiName ++",details: \n" ++ message ) ) + ({model | mode = if model.mode == Loading then RuleTable else model.mode, ui = { modelUi | loadingRules = False, saving = False}}, errorNotification ("Error when "++apiName ++",details: \n" ++ message ) ) getUrl : Model -> String getUrl model = model.contextPath ++ "/secure/configurationManager/ruleManagement" diff --git a/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/View.elm b/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/View.elm index 3616f55b5f9..c2429648c10 100644 --- a/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/View.elm +++ b/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/View.elm @@ -157,7 +157,7 @@ view model = , div [ class "modal-footer" ] [ button [ class "btn btn-default", onClick (ClosePopup Ignore) ] [ text "Cancel " ] - , button [ class "btn btn-danger", onClick (ClosePopup (CallApi (deleteRule rule))) ] + , button [ class "btn btn-danger", onClick (ClosePopup (CallApi False (deleteRule rule))) ] [ text "Delete " , i [ class "fa fa-times-circle" ] [] ] @@ -206,7 +206,7 @@ view model = , div [ class "modal-footer" ] [ button [ class "btn btn-default", onClick (ClosePopup Ignore) ] [ text "Cancel " ] - , button [ class "btn btn-danger", onClick (ClosePopup (CallApi (deleteCategory category))) ] + , button [ class "btn btn-danger", onClick (ClosePopup (CallApi False (deleteCategory category))) ] [ text "Delete " , i [ class "fa fa-times-circle" ] [] ] diff --git a/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/ViewCategoryDetails.elm b/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/ViewCategoryDetails.elm index f36afbf515f..749e658ad9c 100644 --- a/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/ViewCategoryDetails.elm +++ b/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/ViewCategoryDetails.elm @@ -11,6 +11,8 @@ import String exposing ( fromFloat) import NaturalOrdering exposing (compareOn) import ApiCalls exposing (..) import ViewTabContent exposing (buildListCategories) +import ViewUtils exposing (btnSave) + -- -- This file contains all methods to display the details of the selected category. @@ -83,8 +85,7 @@ editionTemplateCat model details = [ button [ class "btn btn-danger" , onClick (OpenDeletionPopupCat category)] [ text "Delete", i [ class "fa fa-times-circle"][]] ] - , button [class "btn btn-success", type_ "button", onClick (CallApi (saveCategoryDetails category details.parentId (Maybe.Extra.isNothing details.originCategory)))] - [ text "Save", i [ class "fa fa-download"][]] + , btnSave model.ui.saving False (CallApi True (saveCategoryDetails category details.parentId (Maybe.Extra.isNothing details.originCategory))) ] else [] diff --git a/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/ViewRuleDetails.elm b/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/ViewRuleDetails.elm index 52f32f8babd..825477e4386 100644 --- a/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/ViewRuleDetails.elm +++ b/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/ViewRuleDetails.elm @@ -10,7 +10,7 @@ import String import ApiCalls exposing (..) import ViewTabContent exposing (tabContent) import Maybe.Extra -import ViewUtils exposing (badgePolicyMode, countRecentChanges, getRuleNbGroups, getRuleNbNodes, getNbResourcesBadge, getGroupsNbResourcesBadge) +import ViewUtils exposing (badgePolicyMode, countRecentChanges, getRuleNbGroups, getRuleNbNodes, getNbResourcesBadge, getGroupsNbResourcesBadge, btnSave) -- -- This file contains all methods to display the details of the selected rule. @@ -148,7 +148,8 @@ editionTemplate model details = if String.isEmpty (String.trim rule.name) then Ignore else - (CallApi (saveRuleDetails rule (Maybe.Extra.isNothing details.originRule))) + (CallApi True (saveRuleDetails rule (Maybe.Extra.isNothing details.originRule))) + in div [class "main-container"] [ div [class "main-header "] @@ -163,7 +164,7 @@ editionTemplate model details = :: ( if model.ui.hasWriteRights then [ div [ class "btn-group" ] topButtons - , button [class "btn btn-success", type_ "button", disabled (String.isEmpty (String.trim rule.name)), onClick saveAction][text "Save", i [ class "fa fa-download"] []] + , btnSave model.ui.saving (String.isEmpty (String.trim rule.name)) saveAction ] else [] @@ -227,4 +228,4 @@ editionTemplate model details = ] , div [class "main-details"] [ tabContent model details ] - ] \ No newline at end of file + ] diff --git a/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/ViewUtils.elm b/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/ViewUtils.elm index abdb00cd351..6fbf6fe8e43 100644 --- a/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/ViewUtils.elm +++ b/webapp/sources/rudder/rudder-web/src/main/elm/rules/sources/ViewUtils.elm @@ -657,7 +657,6 @@ getNodeLink contextPath id = getNodeHostname : Model -> String -> String getNodeHostname model id = (Maybe.withDefault (NodeInfo id id "" "" ) (Dict.get id model.nodes)).hostname - goToBtn : String -> Html Msg goToBtn link = a [ class "btn-goto", href link , onCustomClick (GoTo link)] [ i[class "fa fa-pen"][] ] @@ -704,4 +703,12 @@ getGroupsNbResourcesBadge nbTargets nbInclude msg = span [class ("nb-resources" ++ warningClass), title warningTitle] [ text (String.fromInt nbTargets) , warningIcon - ] \ No newline at end of file + ] + +btnSave : Bool -> Bool -> Msg -> Html Msg +btnSave saving disable action = + let + icon = if saving then i [ class "fa fa-spinner fa-pulse"] [] else i [ class "fa fa-download"] [] + in + button [class ("btn btn-success btn-save" ++ (if saving then " saving" else "")), type_ "button", disabled (saving || disable), onClick action] + [ icon ] diff --git a/webapp/sources/rudder/rudder-web/src/main/webapp/style/rudder/rudder-template.css b/webapp/sources/rudder/rudder-web/src/main/webapp/style/rudder/rudder-template.css index 8c3e58aae93..f116282cc3f 100644 --- a/webapp/sources/rudder/rudder-web/src/main/webapp/style/rudder/rudder-template.css +++ b/webapp/sources/rudder/rudder-web/src/main/webapp/style/rudder/rudder-template.css @@ -1577,6 +1577,20 @@ ul.applied-list > .add-target > .fa{ font-weight: bold; } +/* === BTN SAVE === */ +.btn.btn-save{ + min-width: 86px; +} +.btn.btn-save:before{ + content: "Save"; +} +.btn.btn-save.saving:before{ + content: "Saving"; +} +.btn.btn-save > .fa.fa-spin{ + position: static; + font-size: inherit; +} /* === KEYFRAMES === */ @keyframes fadein { 0% {opacity: 0; transform:translateX(-100px);} @@ -1586,4 +1600,4 @@ ul.applied-list > .add-target > .fa{ @keyframes load { 0% {left: -180px;} 100% {left: 100%;} -} \ No newline at end of file +}