github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/web/elm/src/Build/StepTree/StepTree.elm (about) 1 module Build.StepTree.StepTree exposing 2 ( extendHighlight 3 , finished 4 , init 5 , setHighlight 6 , setImageCheck 7 , setImageGet 8 , switchTab 9 , toggleStep 10 , toggleStepInitialization 11 , toggleStepSubHeader 12 , tooltip 13 , view 14 ) 15 16 import Ansi.Log 17 import Array exposing (Array) 18 import Assets 19 import Build.Models exposing (StepHeaderType(..)) 20 import Build.StepTree.Models 21 exposing 22 ( HookedStep 23 , MetadataField 24 , Step 25 , StepName 26 , StepState(..) 27 , StepTree(..) 28 , StepTreeModel 29 , TabFocus(..) 30 , Version 31 , focusTabbed 32 , isActive 33 , lastActive 34 , mostSevereStepState 35 , showStepState 36 , toggleSubHeaderExpanded 37 , treeIsActive 38 , updateAt 39 ) 40 import Build.Styles as Styles 41 import Colors 42 import Concourse exposing (JsonValue(..)) 43 import DateFormat 44 import Dict exposing (Dict) 45 import Duration 46 import HoverState 47 import Html exposing (Html) 48 import Html.Attributes exposing (attribute, class, classList, href, id, style, target) 49 import Html.Events exposing (onClick, onMouseEnter, onMouseLeave) 50 import Json.Encode 51 import List.Extra 52 import Maybe.Extra 53 import Message.Effects exposing (Effect(..), toHtmlID) 54 import Message.Message exposing (DomID(..), Message(..)) 55 import Routes exposing (Highlight(..), StepID, showHighlight) 56 import StrictEvents 57 import Time 58 import Tooltip 59 import Url 60 import Views.DictView as DictView 61 import Views.Icon as Icon 62 import Views.Spinner as Spinner 63 64 65 init : 66 Highlight 67 -> Concourse.BuildResources 68 -> Concourse.BuildPlan 69 -> StepTreeModel 70 init hl resources ({ id, step } as plan) = 71 case step of 72 Concourse.BuildStepTask name -> 73 constructStep id name 74 |> initBottom hl resources plan Task 75 76 Concourse.BuildStepCheck name -> 77 constructStep id name 78 |> initBottom hl resources plan Check 79 80 Concourse.BuildStepGet name version -> 81 constructStep id name 82 |> setupGetStep resources name version 83 |> initBottom hl resources plan Get 84 85 Concourse.BuildStepPut name -> 86 constructStep id name 87 |> initBottom hl resources plan Put 88 89 Concourse.BuildStepArtifactInput name -> 90 constructStep id name 91 |> initBottom hl resources plan ArtifactInput 92 93 Concourse.BuildStepArtifactOutput name -> 94 constructStep id name 95 |> initBottom hl resources plan ArtifactOutput 96 97 Concourse.BuildStepSetPipeline name -> 98 constructStep id name 99 |> initBottom hl resources plan SetPipeline 100 101 Concourse.BuildStepLoadVar name -> 102 constructStep id name 103 |> initBottom hl resources plan LoadVar 104 105 Concourse.BuildStepAggregate plans -> 106 initMultiStep hl resources id Aggregate plans Nothing 107 108 Concourse.BuildStepInParallel plans -> 109 initMultiStep hl resources id InParallel plans Nothing 110 111 Concourse.BuildStepDo plans -> 112 initMultiStep hl resources id Do plans Nothing 113 114 Concourse.BuildStepAcross { vars, steps } -> 115 let 116 ( values, plans ) = 117 List.unzip steps 118 in 119 constructStep id (String.join ", " vars) 120 |> (\s -> 121 { s 122 | expandedHeaders = 123 plans 124 |> List.indexedMap (\i p -> ( i, planIsHighlighted hl p )) 125 |> List.filter Tuple.second 126 |> Dict.fromList 127 } 128 ) 129 |> Just 130 |> initMultiStep hl resources id (Across id vars values) (Array.fromList plans) 131 |> (\model -> 132 List.foldl 133 (\plan_ -> 134 updateAt plan_.id (\s -> { s | expanded = True }) 135 ) 136 model 137 plans 138 ) 139 140 Concourse.BuildStepRetry plans -> 141 constructStep id "retry" 142 |> (\s -> { s | tabFocus = startingTab hl (Array.toList plans) }) 143 |> Just 144 |> initMultiStep hl resources id (Retry id) plans 145 146 Concourse.BuildStepOnSuccess hookedPlan -> 147 initHookedStep hl resources OnSuccess hookedPlan 148 149 Concourse.BuildStepOnFailure hookedPlan -> 150 initHookedStep hl resources OnFailure hookedPlan 151 152 Concourse.BuildStepOnAbort hookedPlan -> 153 initHookedStep hl resources OnAbort hookedPlan 154 155 Concourse.BuildStepOnError hookedPlan -> 156 initHookedStep hl resources OnError hookedPlan 157 158 Concourse.BuildStepEnsure hookedPlan -> 159 initHookedStep hl resources Ensure hookedPlan 160 161 Concourse.BuildStepTry subPlan -> 162 initWrappedStep hl resources Try subPlan 163 164 Concourse.BuildStepTimeout subPlan -> 165 initWrappedStep hl resources Timeout subPlan 166 167 168 setImageCheck : StepID -> Concourse.BuildPlan -> StepTreeModel -> StepTreeModel 169 setImageCheck stepId subPlan model = 170 let 171 sub = 172 init model.highlight model.resources subPlan 173 in 174 { model 175 | steps = 176 Dict.union sub.steps model.steps 177 |> Dict.update stepId (Maybe.map (\step -> { step | imageCheck = Just sub.tree })) 178 } 179 180 181 setImageGet : StepID -> Concourse.BuildPlan -> StepTreeModel -> StepTreeModel 182 setImageGet stepId subPlan model = 183 let 184 sub = 185 init model.highlight model.resources subPlan 186 in 187 { model 188 | steps = 189 Dict.union sub.steps model.steps 190 |> Dict.update stepId (Maybe.map (\step -> { step | imageGet = Just sub.tree })) 191 } 192 193 194 planIsHighlighted : Highlight -> Concourse.BuildPlan -> Bool 195 planIsHighlighted hl plan = 196 case hl of 197 HighlightNothing -> 198 False 199 200 HighlightLine stepID _ -> 201 planContainsID stepID plan 202 203 HighlightRange stepID _ _ -> 204 planContainsID stepID plan 205 206 207 planContainsID : StepID -> Concourse.BuildPlan -> Bool 208 planContainsID stepID plan = 209 plan |> Concourse.mapBuildPlan .id |> List.member stepID 210 211 212 startingTab : Highlight -> List Concourse.BuildPlan -> TabFocus 213 startingTab hl plans = 214 let 215 idx = 216 case hl of 217 HighlightNothing -> 218 Nothing 219 220 HighlightLine stepID _ -> 221 plans |> List.Extra.findIndex (planContainsID stepID) 222 223 HighlightRange stepID _ _ -> 224 plans |> List.Extra.findIndex (planContainsID stepID) 225 in 226 case idx of 227 Nothing -> 228 Auto 229 230 Just tab -> 231 Manual tab 232 233 234 initBottom : Highlight -> Concourse.BuildResources -> Concourse.BuildPlan -> (StepID -> StepTree) -> Step -> StepTreeModel 235 initBottom hl resources plan construct step = 236 { tree = construct plan.id 237 , steps = Dict.singleton plan.id (expand plan hl step) 238 , highlight = hl 239 , resources = resources 240 } 241 242 243 initMultiStep : 244 Highlight 245 -> Concourse.BuildResources 246 -> StepID 247 -> (Array StepTree -> StepTree) 248 -> Array Concourse.BuildPlan 249 -> Maybe Step 250 -> StepTreeModel 251 initMultiStep hl resources stepId constructor plans rootStep = 252 let 253 inited = 254 Array.map (init hl resources) plans 255 256 trees = 257 Array.map .tree inited 258 259 selfFoci = 260 case rootStep of 261 Nothing -> 262 Dict.empty 263 264 Just step -> 265 Dict.singleton stepId step 266 in 267 { tree = constructor trees 268 , steps = 269 inited 270 |> Array.map .steps 271 |> Array.foldr Dict.union selfFoci 272 , highlight = hl 273 , resources = resources 274 } 275 276 277 constructStep : StepID -> StepName -> Step 278 constructStep stepId name = 279 { id = stepId 280 , name = name 281 , state = StepStatePending 282 , log = Ansi.Log.init Ansi.Log.Cooked 283 , error = Nothing 284 , expanded = False 285 , version = Nothing 286 , metadata = [] 287 , changed = False 288 , timestamps = Dict.empty 289 , initialize = Nothing 290 , start = Nothing 291 , finish = Nothing 292 , tabFocus = Auto 293 , expandedHeaders = Dict.empty 294 , initializationExpanded = False 295 , imageCheck = Nothing 296 , imageGet = Nothing 297 } 298 299 300 expand : Concourse.BuildPlan -> Highlight -> Step -> Step 301 expand plan hl step = 302 { step 303 | expanded = 304 case hl of 305 HighlightNothing -> 306 False 307 308 HighlightLine stepID _ -> 309 List.member stepID (Concourse.mapBuildPlan .id plan) 310 311 HighlightRange stepID _ _ -> 312 List.member stepID (Concourse.mapBuildPlan .id plan) 313 } 314 315 316 initWrappedStep : 317 Highlight 318 -> Concourse.BuildResources 319 -> (StepTree -> StepTree) 320 -> Concourse.BuildPlan 321 -> StepTreeModel 322 initWrappedStep hl resources create plan = 323 let 324 { tree, steps } = 325 init hl resources plan 326 in 327 { tree = create tree 328 , steps = steps 329 , highlight = hl 330 , resources = resources 331 } 332 333 334 initHookedStep : 335 Highlight 336 -> Concourse.BuildResources 337 -> (HookedStep -> StepTree) 338 -> Concourse.HookedPlan 339 -> StepTreeModel 340 initHookedStep hl resources create hookedPlan = 341 let 342 stepModel = 343 init hl resources hookedPlan.step 344 345 hookModel = 346 init hl resources hookedPlan.hook 347 in 348 { tree = create { step = stepModel.tree, hook = hookModel.tree } 349 , steps = Dict.union stepModel.steps hookModel.steps 350 , highlight = hl 351 , resources = resources 352 } 353 354 355 setupGetStep : Concourse.BuildResources -> StepName -> Maybe Version -> Step -> Step 356 setupGetStep resources name version step = 357 { step 358 | version = version 359 , changed = isFirstOccurrence resources.inputs name 360 } 361 362 363 isFirstOccurrence : List Concourse.BuildResourcesInput -> StepName -> Bool 364 isFirstOccurrence resources step = 365 case resources of 366 [] -> 367 False 368 369 { name, firstOccurrence } :: rest -> 370 if name == step then 371 firstOccurrence 372 373 else 374 isFirstOccurrence rest step 375 376 377 finished : StepTreeModel -> StepTreeModel 378 finished model = 379 { model | steps = Dict.map (always finishStep) model.steps } 380 381 382 finishStep : Step -> Step 383 finishStep step = 384 let 385 newState = 386 case step.state of 387 StepStateRunning -> 388 StepStateInterrupted 389 390 StepStatePending -> 391 StepStateCancelled 392 393 otherwise -> 394 otherwise 395 in 396 { step | state = newState } 397 398 399 toggleStep : StepID -> StepTreeModel -> ( StepTreeModel, List Effect ) 400 toggleStep id root = 401 ( updateAt id (\step -> { step | expanded = not step.expanded }) root 402 , [] 403 ) 404 405 406 toggleStepInitialization : StepID -> StepTreeModel -> ( StepTreeModel, List Effect ) 407 toggleStepInitialization id root = 408 ( updateAt id (\step -> { step | initializationExpanded = not step.initializationExpanded }) root 409 , [] 410 ) 411 412 413 toggleStepSubHeader : StepID -> Int -> StepTreeModel -> ( StepTreeModel, List Effect ) 414 toggleStepSubHeader id i root = 415 ( updateAt id (toggleSubHeaderExpanded i) root, [] ) 416 417 418 switchTab : StepID -> Int -> StepTreeModel -> ( StepTreeModel, List Effect ) 419 switchTab id tab root = 420 ( updateAt id (focusTabbed tab) root, [] ) 421 422 423 setHighlight : StepID -> Int -> StepTreeModel -> ( StepTreeModel, List Effect ) 424 setHighlight id line root = 425 let 426 hl = 427 HighlightLine id line 428 in 429 ( { root | highlight = hl }, [ ModifyUrl (showHighlight hl) ] ) 430 431 432 extendHighlight : StepID -> Int -> StepTreeModel -> ( StepTreeModel, List Effect ) 433 extendHighlight id line root = 434 let 435 hl = 436 case root.highlight of 437 HighlightNothing -> 438 HighlightLine id line 439 440 HighlightLine currentID currentLine -> 441 if currentID == id then 442 if currentLine < line then 443 HighlightRange id currentLine line 444 445 else 446 HighlightRange id line currentLine 447 448 else 449 HighlightLine id line 450 451 HighlightRange currentID currentLine _ -> 452 if currentID == id then 453 if currentLine < line then 454 HighlightRange id currentLine line 455 456 else 457 HighlightRange id line currentLine 458 459 else 460 HighlightLine id line 461 in 462 ( { root | highlight = hl }, [ ModifyUrl (showHighlight hl) ] ) 463 464 465 view : 466 { timeZone : Time.Zone, hovered : HoverState.HoverState } 467 -> StepTreeModel 468 -> Html Message 469 view session model = 470 viewTree session model model.tree 0 471 472 473 assumeStep : StepTreeModel -> StepID -> (Step -> Html Message) -> Html Message 474 assumeStep model stepId f = 475 case Dict.get stepId model.steps of 476 Nothing -> 477 -- should be impossible 478 Html.text "" 479 480 Just step -> 481 f step 482 483 484 viewTree : 485 { timeZone : Time.Zone, hovered : HoverState.HoverState } 486 -> StepTreeModel 487 -> StepTree 488 -> Int 489 -> Html Message 490 viewTree session model tree depth = 491 case tree of 492 Task stepId -> 493 viewStep model session depth stepId StepHeaderTask 494 495 Check stepId -> 496 viewStep model session depth stepId StepHeaderCheck 497 498 Get stepId -> 499 viewStep model session depth stepId StepHeaderGet 500 501 Put stepId -> 502 viewStep model session depth stepId StepHeaderPut 503 504 ArtifactInput stepId -> 505 viewStep model session depth stepId StepHeaderGet 506 507 ArtifactOutput stepId -> 508 viewStep model session depth stepId StepHeaderPut 509 510 SetPipeline stepId -> 511 viewStep model session depth stepId StepHeaderSetPipeline 512 513 LoadVar stepId -> 514 viewStep model session depth stepId StepHeaderLoadVar 515 516 Try subTree -> 517 viewTree session model subTree depth 518 519 Across stepId vars vals substeps -> 520 assumeStep model stepId <| 521 \step -> 522 viewStepWithBody model session depth step StepHeaderAcross <| 523 (vals 524 |> List.indexedMap 525 (\i vals_ -> 526 ( vals_ 527 , Dict.get stepId model.steps 528 |> Maybe.andThen (.expandedHeaders >> Dict.get i) 529 |> Maybe.withDefault False 530 , substeps |> Array.get i 531 ) 532 ) 533 |> List.filterMap 534 (\( vals_, expanded_, substep ) -> 535 case substep of 536 Nothing -> 537 -- impossible, but need to get rid of the Maybe 538 Nothing 539 540 Just substep_ -> 541 Just ( vals_, expanded_, substep_ ) 542 ) 543 |> List.indexedMap 544 (\i ( vals_, expanded_, substep ) -> 545 let 546 keyVals = 547 List.map2 Tuple.pair vars vals_ 548 in 549 viewAcrossStepSubHeader model session step.id i keyVals expanded_ (depth + 1) substep 550 ) 551 ) 552 553 Retry stepId steps -> 554 assumeStep model stepId <| 555 \{ tabFocus } -> 556 let 557 activeTab = 558 case tabFocus of 559 Manual i -> 560 i 561 562 Auto -> 563 Maybe.withDefault 0 (lastActive model steps) 564 in 565 Html.div [ class "retry" ] 566 [ Html.ul 567 (class "retry-tabs" :: Styles.retryTabList) 568 (Array.toList <| Array.indexedMap (viewRetryTab session model stepId activeTab) steps) 569 , case Array.get activeTab steps of 570 Just step -> 571 viewTree session model step depth 572 573 Nothing -> 574 -- impossible (bogus tab selected) 575 Html.text "" 576 ] 577 578 Timeout subTree -> 579 viewTree session model subTree depth 580 581 Aggregate trees -> 582 Html.div [ class "aggregate" ] 583 (Array.toList <| Array.map (viewSeq session model depth) trees) 584 585 InParallel trees -> 586 Html.div [ class "parallel" ] 587 (Array.toList <| Array.map (viewSeq session model depth) trees) 588 589 Do trees -> 590 Html.div [ class "do" ] 591 (Array.toList <| Array.map (viewSeq session model depth) trees) 592 593 OnSuccess { step, hook } -> 594 viewHooked session "success" model depth step hook 595 596 OnFailure { step, hook } -> 597 viewHooked session "failure" model depth step hook 598 599 OnAbort { step, hook } -> 600 viewHooked session "abort" model depth step hook 601 602 OnError { step, hook } -> 603 viewHooked session "error" model depth step hook 604 605 Ensure { step, hook } -> 606 viewHooked session "ensure" model depth step hook 607 608 609 viewAcrossStepSubHeader : 610 StepTreeModel 611 -> { timeZone : Time.Zone, hovered : HoverState.HoverState } 612 -> StepID 613 -> Int 614 -> List ( String, JsonValue ) 615 -> Bool 616 -> Int 617 -> StepTree 618 -> Html Message 619 viewAcrossStepSubHeader model session stepID subHeaderIdx keyVals expanded depth subtree = 620 let 621 state = 622 mostSevereStepState model subtree 623 in 624 Html.div 625 [ classList 626 [ ( "build-step", True ) 627 , ( "inactive", not <| isActive state ) 628 ] 629 , style "margin-top" "10px" 630 ] 631 [ Html.div 632 ([ class "header" 633 , class "sub-header" 634 , onClick <| Click <| StepSubHeader stepID subHeaderIdx 635 , style "z-index" <| String.fromInt <| max (maxDepth - depth) 1 636 ] 637 ++ Styles.stepHeader state 638 ) 639 [ Html.div 640 [ style "display" "flex" ] 641 [ viewAcrossStepSubHeaderLabels keyVals ] 642 , Html.div 643 [ style "display" "flex" ] 644 [ viewStepStateWithoutTooltip state ] 645 ] 646 , if expanded then 647 Html.div 648 [ class "step-body" 649 , class "clearfix" 650 , style "padding-bottom" "0" 651 ] 652 [ viewTree session model subtree (depth + 1) ] 653 654 else 655 Html.text "" 656 ] 657 658 659 viewAcrossStepSubHeaderLabels : List ( String, JsonValue ) -> Html Message 660 viewAcrossStepSubHeaderLabels keyVals = 661 Html.div Styles.acrossStepSubHeaderLabel 662 (keyVals 663 |> List.concatMap 664 (\( k, v ) -> 665 viewAcrossStepSubHeaderKeyValue k v 666 ) 667 ) 668 669 670 viewAcrossStepSubHeaderKeyValue : String -> JsonValue -> List (Html Message) 671 viewAcrossStepSubHeaderKeyValue key val = 672 let 673 keyValueSpan text = 674 [ Html.span 675 [ style "display" "inline-block" 676 , style "margin-right" "10px" 677 ] 678 [ Html.span [ style "color" Colors.pending ] 679 [ Html.text <| key ++ ": " ] 680 , Html.text text 681 ] 682 ] 683 in 684 case val of 685 JsonString s -> 686 keyValueSpan s 687 688 JsonNumber n -> 689 keyValueSpan <| String.fromFloat n 690 691 JsonRaw v -> 692 keyValueSpan <| Json.Encode.encode 0 v 693 694 JsonArray l -> 695 List.indexedMap 696 (\i v -> 697 let 698 subKey = 699 key ++ "[" ++ String.fromInt i ++ "]" 700 in 701 viewAcrossStepSubHeaderKeyValue subKey v 702 ) 703 l 704 |> List.concat 705 706 JsonObject o -> 707 List.concatMap 708 (\( k, v ) -> 709 let 710 subKey = 711 key ++ "." ++ k 712 in 713 viewAcrossStepSubHeaderKeyValue subKey v 714 ) 715 o 716 717 718 viewRetryTab : 719 { r | hovered : HoverState.HoverState } 720 -> StepTreeModel 721 -> StepID 722 -> Int 723 -> Int 724 -> StepTree 725 -> Html Message 726 viewRetryTab { hovered } model stepId activeTab tab step = 727 let 728 label = 729 String.fromInt (tab + 1) 730 731 active = 732 treeIsActive model step 733 734 current = 735 activeTab == tab 736 in 737 Html.li 738 ([ classList 739 [ ( "current", current ) 740 , ( "inactive", not active ) 741 ] 742 , onMouseEnter <| Hover <| Just <| StepTab stepId tab 743 , onMouseLeave <| Hover Nothing 744 , onClick <| Click <| StepTab stepId tab 745 ] 746 ++ Styles.tab 747 { isHovered = HoverState.isHovered (StepTab stepId tab) hovered 748 , isCurrent = current 749 , isStarted = active 750 } 751 ) 752 [ Html.text label ] 753 754 755 viewSeq : { timeZone : Time.Zone, hovered : HoverState.HoverState } -> StepTreeModel -> Int -> StepTree -> Html Message 756 viewSeq session model depth tree = 757 Html.div [ class "seq" ] [ viewTree session model tree depth ] 758 759 760 viewHooked : { timeZone : Time.Zone, hovered : HoverState.HoverState } -> String -> StepTreeModel -> Int -> StepTree -> StepTree -> Html Message 761 viewHooked session name model depth step hook = 762 Html.div [ class "hooked" ] 763 [ Html.div [ class "step" ] [ viewTree session model step depth ] 764 , Html.div [ class "children" ] 765 [ Html.div [ class ("hook hook-" ++ name) ] [ viewTree session model hook depth ] 766 ] 767 ] 768 769 770 maxDepth : Int 771 maxDepth = 772 10 773 774 775 viewStepWithBody : 776 StepTreeModel 777 -> { timeZone : Time.Zone, hovered : HoverState.HoverState } 778 -> Int 779 -> Step 780 -> StepHeaderType 781 -> List (Html Message) 782 -> Html Message 783 viewStepWithBody model session depth step headerType body = 784 Html.div 785 [ classList 786 [ ( "build-step", True ) 787 , ( "inactive", not <| isActive step.state ) 788 ] 789 , attribute "data-step-name" step.name 790 ] 791 [ Html.div 792 ([ class "header" 793 , onClick <| Click <| StepHeader step.id 794 , style "z-index" <| String.fromInt <| max (maxDepth - depth) 1 795 ] 796 ++ Styles.stepHeader step.state 797 ) 798 [ Html.div 799 [ style "display" "flex" ] 800 [ viewStepHeaderLabel headerType step.changed step.id 801 , Html.h3 [] [ Html.text step.name ] 802 ] 803 , Html.div 804 [ style "display" "flex" ] 805 [ viewVersion step.version 806 , case Maybe.Extra.or step.imageCheck step.imageGet of 807 Just _ -> 808 viewInitializationToggle step 809 810 Nothing -> 811 Html.text "" 812 , viewStepState step.state step.id 813 ] 814 ] 815 , if step.initializationExpanded then 816 Html.div (class "sub-steps" :: Styles.imageSteps) 817 [ case step.imageCheck of 818 Just subTree -> 819 Html.div [ class "seq" ] 820 [ viewTree session model subTree (depth + 1) 821 ] 822 823 Nothing -> 824 Html.text "" 825 , case step.imageGet of 826 Just subTree -> 827 Html.div [ class "seq" ] 828 [ viewTree session model subTree (depth + 1) 829 ] 830 831 Nothing -> 832 Html.text "" 833 ] 834 835 else 836 Html.text "" 837 , if step.expanded then 838 Html.div 839 [ class "step-body" 840 , class "clearfix" 841 ] 842 ([ viewMetadata step.metadata 843 , Html.pre [ class "timestamped-logs" ] <| 844 viewLogs step.log step.timestamps model.highlight session.timeZone step.id 845 , case step.error of 846 Nothing -> 847 Html.span [] [] 848 849 Just msg -> 850 Html.span [ class "error" ] [ Html.pre [] [ Html.text msg ] ] 851 ] 852 ++ body 853 ) 854 855 else 856 Html.text "" 857 ] 858 859 860 viewInitializationToggle : Step -> Html Message 861 viewInitializationToggle step = 862 let 863 domId = 864 StepInitialization step.id 865 in 866 Html.h3 867 ([ StrictEvents.onLeftClickStopPropagation (Click domId) 868 , onMouseLeave <| Hover Nothing 869 , onMouseEnter <| Hover (Just domId) 870 , id (toHtmlID domId) 871 ] 872 ++ Styles.initializationToggle step.initializationExpanded 873 ) 874 [ Icon.icon 875 { sizePx = 14 876 , image = Assets.CogsIcon 877 } 878 [ style "margin" "7px 0" 879 , style "background-size" "contain" 880 ] 881 ] 882 883 884 viewStep : StepTreeModel -> { timeZone : Time.Zone, hovered : HoverState.HoverState } -> Int -> StepID -> StepHeaderType -> Html Message 885 viewStep model session depth stepId headerType = 886 assumeStep model stepId <| 887 \step -> 888 viewStepWithBody model session depth step headerType [] 889 890 891 viewLogs : 892 Ansi.Log.Model 893 -> Dict Int Time.Posix 894 -> Highlight 895 -> Time.Zone 896 -> String 897 -> List (Html Message) 898 viewLogs { lines } timestamps hl timeZone id = 899 Array.toList <| 900 Array.indexedMap 901 (\idx line -> 902 viewTimestampedLine 903 { timestamps = timestamps 904 , highlight = hl 905 , id = id 906 , lineNo = idx + 1 907 , line = line 908 , timeZone = timeZone 909 } 910 ) 911 lines 912 913 914 viewTimestampedLine : 915 { timestamps : Dict Int Time.Posix 916 , highlight : Highlight 917 , id : StepID 918 , lineNo : Int 919 , line : Ansi.Log.Line 920 , timeZone : Time.Zone 921 } 922 -> Html Message 923 viewTimestampedLine { timestamps, highlight, id, lineNo, line, timeZone } = 924 let 925 highlighted = 926 case highlight of 927 HighlightNothing -> 928 False 929 930 HighlightLine hlId hlLine -> 931 hlId == id && hlLine == lineNo 932 933 HighlightRange hlId hlLine1 hlLine2 -> 934 hlId == id && lineNo >= hlLine1 && lineNo <= hlLine2 935 936 ts = 937 Dict.get lineNo timestamps 938 in 939 Html.tr 940 [ classList 941 [ ( "timestamped-line", True ) 942 , ( "highlighted-line", highlighted ) 943 ] 944 , Html.Attributes.id <| id ++ ":" ++ String.fromInt lineNo 945 ] 946 [ viewTimestamp 947 { id = id 948 , lineNo = lineNo 949 , date = ts 950 , timeZone = timeZone 951 } 952 , viewLine line 953 ] 954 955 956 viewLine : Ansi.Log.Line -> Html Message 957 viewLine line = 958 Html.td [ class "timestamped-content" ] 959 [ Ansi.Log.viewLine line 960 ] 961 962 963 viewTimestamp : 964 { id : String 965 , lineNo : Int 966 , date : Maybe Time.Posix 967 , timeZone : Time.Zone 968 } 969 -> Html Message 970 viewTimestamp { id, lineNo, date, timeZone } = 971 Html.a 972 [ href (showHighlight (HighlightLine id lineNo)) 973 , StrictEvents.onLeftClickOrShiftLeftClick 974 (SetHighlight id lineNo) 975 (ExtendHighlight id lineNo) 976 ] 977 [ case date of 978 Just d -> 979 Html.td 980 [ class "timestamp" ] 981 [ Html.text <| 982 DateFormat.format 983 [ DateFormat.hourMilitaryFixed 984 , DateFormat.text ":" 985 , DateFormat.minuteFixed 986 , DateFormat.text ":" 987 , DateFormat.secondFixed 988 ] 989 timeZone 990 d 991 ] 992 993 _ -> 994 Html.td [ class "timestamp placeholder" ] [] 995 ] 996 997 998 viewVersion : Maybe Version -> Html Message 999 viewVersion version = 1000 Maybe.withDefault Dict.empty version 1001 |> Dict.map (always Html.text) 1002 |> DictView.view [] 1003 1004 1005 viewMetadata : List MetadataField -> Html Message 1006 viewMetadata meta = 1007 let 1008 val value = 1009 case Url.fromString value of 1010 Just _ -> 1011 Html.a 1012 [ href value 1013 , target "_blank" 1014 , style "text-decoration-line" "underline" 1015 ] 1016 [ Html.text value ] 1017 1018 Nothing -> 1019 Html.text value 1020 1021 tr { name, value } = 1022 Html.tr [] 1023 [ Html.td (Styles.metadataCell Styles.Key) 1024 [ Html.text name ] 1025 , Html.td (Styles.metadataCell Styles.Value) 1026 [ val value ] 1027 ] 1028 in 1029 if meta == [] then 1030 Html.text "" 1031 1032 else 1033 meta 1034 |> List.map tr 1035 |> Html.table Styles.metadataTable 1036 1037 1038 viewStepStateWithoutTooltip : StepState -> Html Message 1039 viewStepStateWithoutTooltip state = 1040 let 1041 attributes = 1042 [ style "position" "relative" ] 1043 in 1044 case state of 1045 StepStateRunning -> 1046 Spinner.spinner 1047 { sizePx = 14 1048 , margin = "7px" 1049 } 1050 1051 StepStatePending -> 1052 Icon.icon 1053 { sizePx = 28 1054 , image = Assets.PendingIcon 1055 } 1056 (attribute "data-step-state" "pending" 1057 :: Styles.stepStatusIcon 1058 ++ attributes 1059 ) 1060 1061 StepStateInterrupted -> 1062 Icon.icon 1063 { sizePx = 28 1064 , image = Assets.InterruptedIcon 1065 } 1066 (attribute "data-step-state" "interrupted" 1067 :: Styles.stepStatusIcon 1068 ++ attributes 1069 ) 1070 1071 StepStateCancelled -> 1072 Icon.icon 1073 { sizePx = 28 1074 , image = Assets.CancelledIcon 1075 } 1076 (attribute "data-step-state" "cancelled" 1077 :: Styles.stepStatusIcon 1078 ++ attributes 1079 ) 1080 1081 StepStateSucceeded -> 1082 Icon.icon 1083 { sizePx = 28 1084 , image = Assets.SuccessCheckIcon 1085 } 1086 (attribute "data-step-state" "succeeded" 1087 :: Styles.stepStatusIcon 1088 ++ attributes 1089 ) 1090 1091 StepStateFailed -> 1092 Icon.icon 1093 { sizePx = 28 1094 , image = Assets.FailureTimesIcon 1095 } 1096 (attribute "data-step-state" "failed" 1097 :: Styles.stepStatusIcon 1098 ++ attributes 1099 ) 1100 1101 StepStateErrored -> 1102 Icon.icon 1103 { sizePx = 28 1104 , image = Assets.ExclamationTriangleIcon 1105 } 1106 (attribute "data-step-state" "errored" 1107 :: Styles.stepStatusIcon 1108 ++ attributes 1109 ) 1110 1111 1112 viewStepState : StepState -> StepID -> Html Message 1113 viewStepState state stepID = 1114 let 1115 attributes = 1116 [ onMouseLeave <| Hover Nothing 1117 , onMouseEnter <| Hover (Just (StepState stepID)) 1118 , id <| toHtmlID <| StepState stepID 1119 , style "position" "relative" 1120 ] 1121 in 1122 case state of 1123 StepStateRunning -> 1124 Spinner.spinner 1125 { sizePx = 14 1126 , margin = "7px" 1127 } 1128 1129 StepStatePending -> 1130 Icon.icon 1131 { sizePx = 28 1132 , image = Assets.PendingIcon 1133 } 1134 (attribute "data-step-state" "pending" 1135 :: Styles.stepStatusIcon 1136 ++ attributes 1137 ) 1138 1139 StepStateInterrupted -> 1140 Icon.icon 1141 { sizePx = 28 1142 , image = Assets.InterruptedIcon 1143 } 1144 (attribute "data-step-state" "interrupted" 1145 :: Styles.stepStatusIcon 1146 ++ attributes 1147 ) 1148 1149 StepStateCancelled -> 1150 Icon.icon 1151 { sizePx = 28 1152 , image = Assets.CancelledIcon 1153 } 1154 (attribute "data-step-state" "cancelled" 1155 :: Styles.stepStatusIcon 1156 ++ attributes 1157 ) 1158 1159 StepStateSucceeded -> 1160 Icon.icon 1161 { sizePx = 28 1162 , image = Assets.SuccessCheckIcon 1163 } 1164 (attribute "data-step-state" "succeeded" 1165 :: Styles.stepStatusIcon 1166 ++ attributes 1167 ) 1168 1169 StepStateFailed -> 1170 Icon.icon 1171 { sizePx = 28 1172 , image = Assets.FailureTimesIcon 1173 } 1174 (attribute "data-step-state" "failed" 1175 :: Styles.stepStatusIcon 1176 ++ attributes 1177 ) 1178 1179 StepStateErrored -> 1180 Icon.icon 1181 { sizePx = 28 1182 , image = Assets.ExclamationTriangleIcon 1183 } 1184 (attribute "data-step-state" "errored" 1185 :: Styles.stepStatusIcon 1186 ++ attributes 1187 ) 1188 1189 1190 viewStepHeaderLabel : StepHeaderType -> Bool -> StepID -> Html Message 1191 viewStepHeaderLabel headerType changed stepID = 1192 let 1193 eventHandlers = 1194 case ( headerType, changed ) of 1195 ( StepHeaderGet, True ) -> 1196 [ onMouseLeave <| Hover Nothing 1197 , onMouseEnter <| Hover <| Just <| ChangedStepLabel stepID "new version" 1198 ] 1199 1200 ( StepHeaderSetPipeline, True ) -> 1201 [ onMouseLeave <| Hover Nothing 1202 , onMouseEnter <| Hover <| Just <| ChangedStepLabel stepID "pipeline config changed" 1203 ] 1204 1205 _ -> 1206 [] 1207 in 1208 Html.div 1209 (id (toHtmlID <| ChangedStepLabel stepID "") 1210 :: Styles.stepHeaderLabel changed 1211 ++ eventHandlers 1212 ) 1213 [ Html.text <| 1214 case headerType of 1215 StepHeaderGet -> 1216 "get:" 1217 1218 StepHeaderPut -> 1219 "put:" 1220 1221 StepHeaderTask -> 1222 "task:" 1223 1224 StepHeaderCheck -> 1225 "check:" 1226 1227 StepHeaderSetPipeline -> 1228 "set_pipeline:" 1229 1230 StepHeaderLoadVar -> 1231 "load_var:" 1232 1233 StepHeaderAcross -> 1234 "across:" 1235 ] 1236 1237 1238 tooltip : StepTreeModel -> { a | hovered : HoverState.HoverState } -> Maybe Tooltip.Tooltip 1239 tooltip model { hovered } = 1240 case hovered of 1241 HoverState.Tooltip (ChangedStepLabel _ text) _ -> 1242 Just 1243 { body = 1244 Html.div 1245 Styles.changedStepTooltip 1246 [ Html.text text ] 1247 , attachPosition = 1248 { direction = Tooltip.Top 1249 , alignment = Tooltip.Start 1250 } 1251 , arrow = Just { size = 5, color = Colors.tooltipBackground } 1252 } 1253 1254 HoverState.Tooltip (StepInitialization _) _ -> 1255 Just 1256 { body = 1257 Html.div 1258 Styles.changedStepTooltip 1259 [ Html.text "image fetching" ] 1260 , attachPosition = 1261 { direction = Tooltip.Top 1262 , alignment = Tooltip.End 1263 } 1264 , arrow = Just { size = 5, color = Colors.tooltipBackground } 1265 } 1266 1267 HoverState.Tooltip (StepState id) _ -> 1268 Dict.get id model.steps 1269 |> Maybe.map stepDurationTooltip 1270 1271 _ -> 1272 Nothing 1273 1274 1275 stepDurationTooltip : Step -> Tooltip.Tooltip 1276 stepDurationTooltip { state, initialize, start, finish } = 1277 { body = 1278 Html.div Styles.durationTooltip 1279 [ case ( initialize, start, finish ) of 1280 ( Just initializedAt, Just startedAt, Just finishedAt ) -> 1281 let 1282 initDuration = 1283 Duration.between initializedAt startedAt 1284 1285 stepDuration = 1286 Duration.between startedAt finishedAt 1287 in 1288 DictView.view [] 1289 (Dict.fromList 1290 [ ( "initialization" 1291 , Html.text (Duration.format initDuration) 1292 ) 1293 , ( "step" 1294 , Html.text (Duration.format stepDuration) 1295 ) 1296 ] 1297 ) 1298 1299 _ -> 1300 Html.text (showStepState state) 1301 ] 1302 , attachPosition = 1303 { direction = Tooltip.Top 1304 , alignment = Tooltip.End 1305 } 1306 , arrow = Just { size = 5, color = Colors.tooltipBackground } 1307 }