github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/web/elm/src/Build/Build.elm (about) 1 module Build.Build exposing 2 ( bodyId 3 , changeToBuild 4 , documentTitle 5 , getScrollBehavior 6 , getUpdateMessage 7 , handleCallback 8 , handleDelivery 9 , init 10 , subscriptions 11 , tooltip 12 , update 13 , view 14 ) 15 16 import Api.Endpoints as Endpoints 17 import Application.Models exposing (Session) 18 import Assets 19 import Build.Header.Header as Header 20 import Build.Header.Models exposing (BuildPageType(..), CurrentOutput(..)) 21 import Build.Models exposing (Model, toMaybe) 22 import Build.Output.Models exposing (OutputModel) 23 import Build.Output.Output 24 import Build.Shortcuts as Shortcuts 25 import Build.StepTree.Models as STModels 26 import Build.StepTree.StepTree as StepTree 27 import Build.Styles as Styles 28 import Concourse 29 import Concourse.BuildStatus exposing (BuildStatus(..)) 30 import DateFormat 31 import Dict exposing (Dict) 32 import EffectTransformer exposing (ET) 33 import HoverState 34 import Html exposing (Html) 35 import Html.Attributes 36 exposing 37 ( attribute 38 , class 39 , classList 40 , href 41 , id 42 , style 43 , tabindex 44 , title 45 ) 46 import Html.Lazy 47 import Http 48 import List.Extra 49 import Login.Login as Login 50 import Maybe.Extra 51 import Message.Callback exposing (Callback(..)) 52 import Message.Effects as Effects exposing (Effect(..)) 53 import Message.Message exposing (DomID(..), Message(..)) 54 import Message.ScrollDirection as ScrollDirection 55 import Message.Subscription as Subscription exposing (Delivery(..), Interval(..), Subscription(..)) 56 import Message.TopLevelMessage exposing (TopLevelMessage(..)) 57 import Routes 58 import SideBar.SideBar as SideBar 59 import StrictEvents exposing (onScroll) 60 import String 61 import Time 62 import Tooltip 63 import UpdateMsg exposing (UpdateMsg) 64 import Views.Icon as Icon 65 import Views.LoadingIndicator as LoadingIndicator 66 import Views.NotAuthorized as NotAuthorized 67 import Views.Spinner as Spinner 68 import Views.Styles 69 import Views.TopBar as TopBar 70 71 72 bodyId : String 73 bodyId = 74 "build-body" 75 76 77 type alias Flags = 78 { highlight : Routes.Highlight 79 , pageType : BuildPageType 80 , fromBuildPage : Maybe Build.Header.Models.BuildPageType 81 } 82 83 84 type ScrollBehavior 85 = ScrollWindow 86 | ScrollToID String 87 | NoScroll 88 89 90 init : Flags -> ( Model, List Effect ) 91 init flags = 92 changeToBuild 93 flags 94 ( { page = flags.pageType 95 , id = 0 96 , name = 97 case flags.pageType of 98 OneOffBuildPage id -> 99 String.fromInt id 100 101 JobBuildPage { buildName } -> 102 buildName 103 , now = Nothing 104 , job = Nothing 105 , disableManualTrigger = False 106 , history = [] 107 , nextPage = Nothing 108 , prep = Nothing 109 , duration = { startedAt = Nothing, finishedAt = Nothing } 110 , status = BuildStatusPending 111 , output = Empty 112 , autoScroll = True 113 , isScrollToIdInProgress = False 114 , previousKeyPress = Nothing 115 , isTriggerBuildKeyDown = False 116 , showHelp = False 117 , highlight = flags.highlight 118 , authorized = True 119 , fetchingHistory = False 120 , scrolledToCurrentBuild = False 121 , shiftDown = False 122 , isUserMenuExpanded = False 123 , hasLoadedYet = False 124 , notFound = False 125 , reapTime = Nothing 126 } 127 , [ GetCurrentTime 128 , GetCurrentTimeZone 129 , FetchAllPipelines 130 ] 131 ) 132 133 134 subscriptions : Model -> List Subscription 135 subscriptions model = 136 let 137 buildEventsUrl = 138 model.output 139 |> toMaybe 140 |> Maybe.andThen .eventStreamUrlPath 141 in 142 [ OnClockTick OneSecond 143 , OnClockTick FiveSeconds 144 , OnKeyDown 145 , OnKeyUp 146 , OnElementVisible 147 , OnScrolledToId 148 ] 149 ++ (case buildEventsUrl of 150 Nothing -> 151 [] 152 153 Just url -> 154 [ Subscription.FromEventSource ( url, [ "end", "event" ] ) ] 155 ) 156 157 158 changeToBuild : Flags -> ET Model 159 changeToBuild { highlight, pageType, fromBuildPage } ( model, effects ) = 160 let 161 newModel = 162 { model | page = pageType } 163 in 164 (if fromBuildPage == Just pageType then 165 ( newModel, effects ) 166 167 else 168 ( { newModel 169 | prep = Nothing 170 , output = Empty 171 , autoScroll = True 172 , highlight = highlight 173 } 174 , case pageType of 175 OneOffBuildPage buildId -> 176 effects 177 ++ [ CloseBuildEventStream, FetchBuild 0 buildId ] 178 179 JobBuildPage jbi -> 180 effects 181 ++ [ CloseBuildEventStream, FetchJobBuild jbi ] 182 ) 183 ) 184 |> Header.changeToBuild pageType 185 186 187 extractTitle : Model -> String 188 extractTitle model = 189 case ( model.hasLoadedYet, model.job, model.page ) of 190 ( True, Just { jobName }, _ ) -> 191 jobName ++ " #" ++ model.name 192 193 ( _, _, JobBuildPage { jobName, buildName } ) -> 194 jobName ++ " #" ++ buildName 195 196 ( _, _, OneOffBuildPage id ) -> 197 "#" ++ String.fromInt id 198 199 200 getUpdateMessage : Model -> UpdateMsg 201 getUpdateMessage model = 202 if model.notFound then 203 UpdateMsg.NotFound 204 205 else 206 UpdateMsg.AOK 207 208 209 handleCallback : Callback -> ET Model 210 handleCallback action ( model, effects ) = 211 (case action of 212 BuildFetched (Ok build) -> 213 handleBuildFetched build ( model, effects ) 214 215 BuildFetched (Err err) -> 216 case err of 217 Http.BadStatus { status } -> 218 if status.code == 401 then 219 ( model, effects ++ [ RedirectToLogin ] ) 220 221 else if status.code == 404 then 222 ( { model 223 | prep = Nothing 224 , notFound = True 225 } 226 , effects 227 ) 228 229 else 230 ( model, effects ) 231 232 _ -> 233 ( model, effects ) 234 235 BuildAborted (Ok ()) -> 236 ( model, effects ) 237 238 BuildPrepFetched buildId (Ok buildPrep) -> 239 if buildId == model.id then 240 handleBuildPrepFetched buildPrep ( model, effects ) 241 242 else 243 ( model, effects ) 244 245 BuildPrepFetched _ (Err err) -> 246 case err of 247 Http.BadStatus { status } -> 248 if status.code == 401 then 249 ( { model | authorized = False }, effects ) 250 251 else 252 ( model, effects ) 253 254 _ -> 255 ( model, effects ) 256 257 PlanAndResourcesFetched buildId (Ok planAndResources) -> 258 updateOutput 259 (Build.Output.Output.planAndResourcesFetched 260 buildId 261 planAndResources 262 ) 263 ( model 264 , effects 265 ++ [ Effects.OpenBuildEventStream 266 { url = 267 Endpoints.BuildEventStream 268 |> Endpoints.Build buildId 269 |> Endpoints.toString [] 270 , eventTypes = [ "end", "event" ] 271 } 272 , SyncStickyBuildLogHeaders 273 ] 274 ) 275 276 PlanAndResourcesFetched _ (Err err) -> 277 case err of 278 Http.BadStatus { status } -> 279 let 280 isAborted = 281 model.status == BuildStatusAborted 282 in 283 if status.code == 404 && isAborted then 284 ( { model | output = Cancelled } 285 , effects 286 ) 287 288 else if status.code == 401 then 289 ( { model | authorized = False }, effects ) 290 291 else 292 ( model, effects ) 293 294 _ -> 295 ( model, effects ) 296 297 BuildJobDetailsFetched (Ok job) -> 298 ( { model | disableManualTrigger = job.disableManualTrigger } 299 , effects 300 ) 301 302 BuildJobDetailsFetched (Err _) -> 303 -- https://github.com/concourse/concourse/issues/3201 304 ( model, effects ) 305 306 _ -> 307 ( model, effects ) 308 ) 309 |> Header.handleCallback action 310 311 312 handleDelivery : { a | hovered : HoverState.HoverState } -> Delivery -> ET Model 313 handleDelivery session delivery ( model, effects ) = 314 (case delivery of 315 ClockTicked OneSecond time -> 316 ( { model | now = Just time }, effects ) 317 318 ClockTicked FiveSeconds _ -> 319 ( model, effects ++ [ Effects.FetchAllPipelines ] ) 320 321 WindowResized _ _ -> 322 ( model, effects ++ [ SyncStickyBuildLogHeaders ] ) 323 324 EventsReceived (Ok envelopes) -> 325 let 326 eventSourceClosed = 327 model.output 328 |> toMaybe 329 |> Maybe.map (.eventSourceOpened >> not) 330 |> Maybe.withDefault False 331 332 buildStatus = 333 envelopes 334 |> List.filterMap 335 (\{ data } -> 336 case data of 337 STModels.BuildStatus status date -> 338 Just ( status, date ) 339 340 _ -> 341 Nothing 342 ) 343 |> List.Extra.last 344 345 ( newModel, newEffects ) = 346 updateOutput 347 (Build.Output.Output.handleEnvelopes envelopes) 348 (if eventSourceClosed && (envelopes |> List.map .data |> List.member STModels.NetworkError) then 349 ( { model | authorized = False }, effects ) 350 351 else 352 case getScrollBehavior model of 353 ScrollWindow -> 354 ( model 355 , effects 356 ++ [ Effects.Scroll 357 ScrollDirection.ToBottom 358 bodyId 359 ] 360 ) 361 362 ScrollToID id -> 363 ( { model 364 | highlight = Routes.HighlightNothing 365 , autoScroll = False 366 , isScrollToIdInProgress = True 367 } 368 , effects 369 ++ [ Effects.Scroll 370 (ScrollDirection.ToId id) 371 bodyId 372 ] 373 ) 374 375 NoScroll -> 376 ( model, effects ) 377 ) 378 in 379 case ( model.hasLoadedYet, buildStatus ) of 380 ( True, Just ( status, _ ) ) -> 381 ( newModel 382 , if Concourse.BuildStatus.isRunning model.status then 383 newEffects ++ [ SetFavIcon (Just status) ] 384 385 else 386 newEffects 387 ) 388 389 _ -> 390 ( newModel, newEffects ) 391 392 ScrolledToId _ -> 393 ( { model | isScrollToIdInProgress = False }, effects ) 394 395 _ -> 396 ( model, effects ) 397 ) 398 |> Tooltip.handleDelivery session delivery 399 |> Shortcuts.handleDelivery delivery 400 |> Header.handleDelivery delivery 401 402 403 update : Message -> ET Model 404 update msg ( model, effects ) = 405 (case msg of 406 Click (BuildTab id name) -> 407 ( model 408 , effects 409 ++ [ NavigateTo <| 410 Routes.toString <| 411 Routes.buildRoute id name model.job 412 ] 413 ) 414 415 Click TriggerBuildButton -> 416 (model.job 417 |> Maybe.map (DoTriggerBuild >> (::) >> Tuple.mapSecond) 418 |> Maybe.withDefault identity 419 ) 420 ( model, effects ) 421 422 Click AbortBuildButton -> 423 ( model, DoAbortBuild model.id :: effects ) 424 425 Click (StepHeader id) -> 426 updateOutput 427 (Build.Output.Output.handleStepTreeMsg <| StepTree.toggleStep id) 428 ( model, effects ++ [ SyncStickyBuildLogHeaders ] ) 429 430 Click (StepInitialization id) -> 431 updateOutput 432 (Build.Output.Output.handleStepTreeMsg <| StepTree.toggleStepInitialization id) 433 ( model, effects ++ [ SyncStickyBuildLogHeaders ] ) 434 435 Click (StepSubHeader id i) -> 436 updateOutput 437 (Build.Output.Output.handleStepTreeMsg <| StepTree.toggleStepSubHeader id i) 438 ( model, effects ++ [ SyncStickyBuildLogHeaders ] ) 439 440 Click (StepTab id tab) -> 441 updateOutput 442 (Build.Output.Output.handleStepTreeMsg <| StepTree.switchTab id tab) 443 ( model, effects ) 444 445 SetHighlight id line -> 446 updateOutput 447 (Build.Output.Output.handleStepTreeMsg <| StepTree.setHighlight id line) 448 ( model, effects ) 449 450 ExtendHighlight id line -> 451 updateOutput 452 (Build.Output.Output.handleStepTreeMsg <| StepTree.extendHighlight id line) 453 ( model, effects ) 454 455 GoToRoute route -> 456 ( model, effects ++ [ NavigateTo <| Routes.toString <| route ] ) 457 458 Scrolled { scrollHeight, scrollTop, clientHeight } -> 459 ( { model 460 | autoScroll = 461 (scrollHeight == scrollTop + clientHeight) 462 && not model.isScrollToIdInProgress 463 } 464 , effects 465 ) 466 467 _ -> 468 ( model, effects ) 469 ) 470 |> Header.update msg 471 472 473 getScrollBehavior : Model -> ScrollBehavior 474 getScrollBehavior model = 475 case model.highlight of 476 Routes.HighlightLine stepID lineNumber -> 477 ScrollToID <| stepID ++ ":" ++ String.fromInt lineNumber 478 479 Routes.HighlightRange stepID beginning end -> 480 if beginning <= end then 481 ScrollToID <| stepID ++ ":" ++ String.fromInt beginning 482 483 else 484 NoScroll 485 486 Routes.HighlightNothing -> 487 if model.autoScroll then 488 if model.hasLoadedYet then 489 case model.status of 490 BuildStatusSucceeded -> 491 NoScroll 492 493 BuildStatusPending -> 494 NoScroll 495 496 _ -> 497 ScrollWindow 498 499 else 500 NoScroll 501 502 else 503 NoScroll 504 505 506 updateOutput : 507 (OutputModel -> ( OutputModel, List Effect )) 508 -> ET Model 509 updateOutput updater ( model, effects ) = 510 case model.output of 511 Output output -> 512 let 513 ( newOutput, outputEffects ) = 514 updater output 515 516 newModel = 517 { model 518 | output = 519 -- model.output must be equal-by-reference 520 -- to its previous value when passed 521 -- into `Html.Lazy.lazy3` below. 522 if newOutput /= output then 523 Output newOutput 524 525 else 526 model.output 527 } 528 in 529 ( newModel, effects ++ outputEffects ) 530 531 _ -> 532 ( model, effects ) 533 534 535 handleBuildFetched : Concourse.Build -> ET Model 536 handleBuildFetched build ( model, effects ) = 537 let 538 withBuild = 539 { model 540 | reapTime = build.reapTime 541 , output = 542 if model.hasLoadedYet then 543 model.output 544 545 else 546 Empty 547 } 548 549 fetchJobAndHistory = 550 case ( model.job, build.job ) of 551 ( Nothing, Just buildJob ) -> 552 [ FetchBuildJobDetails buildJob 553 , FetchBuildHistory buildJob Nothing 554 ] 555 556 _ -> 557 [] 558 559 ( newModel, cmd ) = 560 if build.status == BuildStatusPending then 561 ( withBuild, effects ++ pollUntilStarted build.id ) 562 563 else if build.reapTime == Nothing then 564 case model.prep of 565 Nothing -> 566 initBuildOutput build ( withBuild, effects ) 567 568 Just _ -> 569 let 570 ( newNewModel, newEffects ) = 571 initBuildOutput build ( withBuild, effects ) 572 in 573 ( newNewModel 574 , newEffects 575 ++ [ FetchBuildPrep 1000 build.id ] 576 ) 577 578 else 579 ( withBuild, effects ) 580 in 581 if not model.hasLoadedYet || build.id == model.id then 582 ( newModel 583 , cmd 584 ++ fetchJobAndHistory 585 ++ [ SetFavIcon (Just build.status), Focus bodyId ] 586 ) 587 588 else 589 ( model, effects ) 590 591 592 pollUntilStarted : Int -> List Effect 593 pollUntilStarted buildId = 594 [ FetchBuild 1000 buildId 595 , FetchBuildPrep 1000 buildId 596 ] 597 598 599 initBuildOutput : Concourse.Build -> ET Model 600 initBuildOutput build ( model, effects ) = 601 let 602 ( output, outputCmd ) = 603 Build.Output.Output.init model.highlight build 604 in 605 ( { model | output = Output output } 606 , effects ++ outputCmd 607 ) 608 609 610 handleBuildPrepFetched : Concourse.BuildPrep -> ET Model 611 handleBuildPrepFetched buildPrep ( model, effects ) = 612 ( { model | prep = Just buildPrep } 613 , effects 614 ) 615 616 617 documentTitle : Model -> String 618 documentTitle = 619 extractTitle 620 621 622 view : Session -> Model -> Html Message 623 view session model = 624 let 625 route = 626 case model.page of 627 OneOffBuildPage buildId -> 628 Routes.OneOffBuild 629 { id = buildId 630 , highlight = model.highlight 631 } 632 633 JobBuildPage buildId -> 634 Routes.Build 635 { id = buildId 636 , highlight = model.highlight 637 } 638 in 639 Html.div 640 (id "page-including-top-bar" :: Views.Styles.pageIncludingTopBar) 641 [ Html.div 642 (id "top-bar-app" :: Views.Styles.topBar False) 643 [ SideBar.hamburgerMenu session 644 , TopBar.concourseLogo 645 , breadcrumbs model 646 , Login.view session.userState model 647 ] 648 , Html.div 649 (id "page-below-top-bar" :: Views.Styles.pageBelowTopBar route) 650 [ SideBar.view session 651 (model.job 652 |> Maybe.map 653 (\j -> 654 { pipelineName = j.pipelineName 655 , teamName = j.teamName 656 } 657 ) 658 ) 659 , viewBuildPage session model 660 ] 661 ] 662 663 664 tooltip : Model -> { a | hovered : HoverState.HoverState } -> Maybe Tooltip.Tooltip 665 tooltip model session = 666 model.output 667 |> toMaybe 668 |> Maybe.andThen .steps 669 |> Maybe.andThen (\steps -> StepTree.tooltip steps session) 670 671 672 breadcrumbs : Model -> Html Message 673 breadcrumbs model = 674 case ( model.job, model.page ) of 675 ( Just jobId, _ ) -> 676 TopBar.breadcrumbs <| 677 Routes.Job 678 { id = jobId 679 , page = Nothing 680 } 681 682 ( _, JobBuildPage buildId ) -> 683 TopBar.breadcrumbs <| 684 Routes.Build 685 { id = buildId 686 , highlight = model.highlight 687 } 688 689 _ -> 690 Html.text "" 691 692 693 viewBuildPage : Session -> Model -> Html Message 694 viewBuildPage session model = 695 if model.hasLoadedYet then 696 Html.div 697 [ class "with-fixed-header" 698 , attribute "data-build-name" model.name 699 , style "flex-grow" "1" 700 , style "display" "flex" 701 , style "flex-direction" "column" 702 , style "overflow" "hidden" 703 ] 704 [ Header.view session model 705 , body session model 706 ] 707 708 else 709 LoadingIndicator.view 710 711 712 body : 713 Session 714 -> 715 { a 716 | prep : Maybe Concourse.BuildPrep 717 , job : Maybe Concourse.JobIdentifier 718 , status : BuildStatus 719 , duration : Concourse.BuildDuration 720 , reapTime : Maybe Time.Posix 721 , id : Int 722 , name : String 723 , output : CurrentOutput 724 , authorized : Bool 725 , showHelp : Bool 726 } 727 -> Html Message 728 body session ({ prep, output, authorized, showHelp } as params) = 729 Html.div 730 ([ class "scrollable-body build-body" 731 , id bodyId 732 , tabindex 0 733 , onScroll Scrolled 734 ] 735 ++ Styles.body 736 ) 737 <| 738 if authorized then 739 [ viewBuildPrep prep 740 , Html.Lazy.lazy3 741 viewBuildOutput 742 session.timeZone 743 (Build.Output.Output.filterHoverState session.hovered) 744 output 745 , Shortcuts.keyboardHelp showHelp 746 ] 747 ++ tombstone session.timeZone params 748 749 else 750 [ NotAuthorized.view ] 751 752 753 tombstone : 754 Time.Zone 755 -> 756 { a 757 | job : Maybe Concourse.JobIdentifier 758 , status : BuildStatus 759 , duration : Concourse.BuildDuration 760 , reapTime : Maybe Time.Posix 761 , id : Int 762 , name : String 763 } 764 -> List (Html Message) 765 tombstone timeZone model = 766 let 767 maybeBirthDate = 768 Maybe.Extra.or model.duration.startedAt model.duration.finishedAt 769 in 770 case ( maybeBirthDate, model.reapTime ) of 771 ( Just birthDate, Just reapTime ) -> 772 [ Html.div 773 [ class "tombstone" ] 774 [ Html.div [ class "heading" ] [ Html.text "RIP" ] 775 , Html.div 776 [ class "job-name" ] 777 [ model.job 778 |> Maybe.map .jobName 779 |> Maybe.withDefault "one-off build" 780 |> Html.text 781 ] 782 , Html.div 783 [ class "build-name" ] 784 [ Html.text <| "build #" ++ model.name ] 785 , Html.div 786 [ class "date" ] 787 [ Html.text <| 788 mmDDYY timeZone birthDate 789 ++ "-" 790 ++ mmDDYY timeZone reapTime 791 ] 792 , Html.div 793 [ class "epitaph" ] 794 [ Html.text <| 795 case model.status of 796 BuildStatusSucceeded -> 797 "It passed, and now it has passed on." 798 799 BuildStatusFailed -> 800 "It failed, and now has been forgotten." 801 802 BuildStatusErrored -> 803 "It errored, but has found forgiveness." 804 805 BuildStatusAborted -> 806 "It was never given a chance." 807 808 _ -> 809 "I'm not dead yet." 810 ] 811 ] 812 , Html.div 813 [ class "explanation" ] 814 [ Html.text "This log has been " 815 , Html.a 816 [ Html.Attributes.href "https://concourse-ci.org/jobs.html#job-build-log-retention" ] 817 [ Html.text "reaped." ] 818 ] 819 ] 820 821 _ -> 822 [] 823 824 825 mmDDYY : Time.Zone -> Time.Posix -> String 826 mmDDYY = 827 DateFormat.format 828 [ DateFormat.monthFixed 829 , DateFormat.text "/" 830 , DateFormat.dayOfMonthFixed 831 , DateFormat.text "/" 832 , DateFormat.yearNumberLastTwo 833 ] 834 835 836 viewBuildOutput : Time.Zone -> HoverState.HoverState -> CurrentOutput -> Html Message 837 viewBuildOutput timeZone hovered output = 838 case output of 839 Output o -> 840 Build.Output.Output.view 841 { timeZone = timeZone, hovered = hovered } 842 o 843 844 Cancelled -> 845 Html.div 846 Styles.errorLog 847 [ Html.text "build cancelled" ] 848 849 Empty -> 850 Html.div [] [] 851 852 853 viewBuildPrep : Maybe Concourse.BuildPrep -> Html Message 854 viewBuildPrep buildPrep = 855 case buildPrep of 856 Just prep -> 857 Html.div [ class "build-step" ] 858 [ Html.div 859 [ class "header" 860 , style "display" "flex" 861 , style "align-items" "center" 862 ] 863 [ Icon.icon 864 { sizePx = 14, image = Assets.CogsIcon } 865 [ style "margin" "7px" 866 , style "margin-right" "2px" 867 , style "background-size" "contain" 868 ] 869 , Html.h3 [] [ Html.text "preparing build" ] 870 ] 871 , Html.div [] 872 [ Html.ul 873 [ class "prep-status-list" 874 , style "font-size" "14px" 875 ] 876 ([ viewBuildPrepLi "checking pipeline is not paused" prep.pausedPipeline Dict.empty 877 , viewBuildPrepLi "checking job is not paused" prep.pausedJob Dict.empty 878 ] 879 ++ viewBuildPrepInputs prep.inputs 880 ++ [ viewBuildPrepLi "waiting for a suitable set of input versions" prep.inputsSatisfied prep.missingInputReasons 881 , viewBuildPrepLi "checking max-in-flight is not reached" prep.maxRunningBuilds Dict.empty 882 ] 883 ) 884 ] 885 ] 886 887 Nothing -> 888 Html.div [] [] 889 890 891 viewBuildPrepInputs : Dict String Concourse.BuildPrepStatus -> List (Html Message) 892 viewBuildPrepInputs inputs = 893 List.map viewBuildPrepInput (Dict.toList inputs) 894 895 896 viewBuildPrepInput : ( String, Concourse.BuildPrepStatus ) -> Html Message 897 viewBuildPrepInput ( name, status ) = 898 viewBuildPrepLi ("discovering any new versions of " ++ name) status Dict.empty 899 900 901 viewBuildPrepDetails : Dict String String -> Html Message 902 viewBuildPrepDetails details = 903 Html.ul [ class "details" ] 904 (List.map viewDetailItem (Dict.toList details)) 905 906 907 viewDetailItem : ( String, String ) -> Html Message 908 viewDetailItem ( name, status ) = 909 Html.li [] 910 [ Html.text (name ++ " - " ++ status) ] 911 912 913 viewBuildPrepLi : 914 String 915 -> Concourse.BuildPrepStatus 916 -> Dict String String 917 -> Html Message 918 viewBuildPrepLi text status details = 919 Html.li 920 [ classList 921 [ ( "prep-status", True ) 922 , ( "inactive", status == Concourse.BuildPrepStatusUnknown ) 923 ] 924 ] 925 [ Html.div 926 [ style "align-items" "center" 927 , style "display" "flex" 928 ] 929 [ viewBuildPrepStatus status 930 , Html.span [] 931 [ Html.text text ] 932 ] 933 , viewBuildPrepDetails details 934 ] 935 936 937 viewBuildPrepStatus : Concourse.BuildPrepStatus -> Html Message 938 viewBuildPrepStatus status = 939 case status of 940 Concourse.BuildPrepStatusUnknown -> 941 Html.div 942 [ title "thinking..." ] 943 [ Spinner.spinner 944 { sizePx = 12 945 , margin = "0 8px 0 0" 946 } 947 ] 948 949 Concourse.BuildPrepStatusBlocking -> 950 Html.div 951 [ title "blocking" ] 952 [ Spinner.spinner 953 { sizePx = 12 954 , margin = "0 8px 0 0" 955 } 956 ] 957 958 Concourse.BuildPrepStatusNotBlocking -> 959 Icon.icon 960 { sizePx = 12 961 , image = Assets.NotBlockingCheckIcon 962 } 963 [ style "margin-right" "8px" 964 , style "background-size" "contain" 965 , title "not blocking" 966 ]