github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/app/templates/clients/client/index.hbs (about) 1 {{page-title "Client " (or this.model.name this.model.shortId)}} 2 <ClientSubnav @client={{this.model}} /> 3 <section class="section"> 4 {{#if this.eligibilityError}} 5 <div data-test-eligibility-error class="columns"> 6 <div class="column"> 7 <div class="notification is-danger"> 8 <h3 data-test-title class="title is-4"> 9 Eligibility Error 10 </h3> 11 <p data-test-message> 12 {{this.eligibilityError}} 13 </p> 14 </div> 15 </div> 16 <div class="column is-centered is-minimum"> 17 <button 18 data-test-dismiss 19 class="button is-danger" 20 onclick={{action (mut this.eligibilityError) ""}} 21 type="button" 22 > 23 Okay 24 </button> 25 </div> 26 </div> 27 {{/if}} 28 {{#if this.stopDrainError}} 29 <div data-test-stop-drain-error class="columns"> 30 <div class="column"> 31 <div class="notification is-danger"> 32 <h3 data-test-title class="title is-4"> 33 Stop Drain Error 34 </h3> 35 <p data-test-message> 36 {{this.stopDrainError}} 37 </p> 38 </div> 39 </div> 40 <div class="column is-centered is-minimum"> 41 <button 42 data-test-dismiss 43 class="button is-danger" 44 onclick={{action (mut this.stopDrainError) ""}} 45 type="button" 46 > 47 Okay 48 </button> 49 </div> 50 </div> 51 {{/if}} 52 {{#if this.drainError}} 53 <div data-test-drain-error class="columns"> 54 <div class="column"> 55 <div class="notification is-danger"> 56 <h3 data-test-title class="title is-4"> 57 Drain Error 58 </h3> 59 <p data-test-message> 60 {{this.drainError}} 61 </p> 62 </div> 63 </div> 64 <div class="column is-centered is-minimum"> 65 <button 66 data-test-dismiss 67 class="button is-danger" 68 onclick={{action (mut this.drainError) ""}} 69 type="button" 70 > 71 Okay 72 </button> 73 </div> 74 </div> 75 {{/if}} 76 {{#if this.showDrainStoppedNotification}} 77 <div class="notification is-info"> 78 <div data-test-drain-stopped-notification class="columns"> 79 <div class="column"> 80 <h3 data-test-title class="title is-4"> 81 Drain Stopped 82 </h3> 83 <p data-test-message> 84 The drain has been stopped and the node has been set to ineligible. 85 </p> 86 </div> 87 <div class="column is-centered is-minimum"> 88 <button 89 data-test-dismiss 90 class="button is-info" 91 onclick={{action (mut this.showDrainStoppedNotification) false}} 92 type="button" 93 > 94 Okay 95 </button> 96 </div> 97 </div> 98 </div> 99 {{/if}} 100 {{#if this.showDrainUpdateNotification}} 101 <div class="notification is-info"> 102 <div data-test-drain-updated-notification class="columns"> 103 <div class="column"> 104 <h3 data-test-title class="title is-4"> 105 Drain Updated 106 </h3> 107 <p data-test-message> 108 The new drain specification has been applied. 109 </p> 110 </div> 111 <div class="column is-centered is-minimum"> 112 <button 113 data-test-dismiss 114 class="button is-info" 115 onclick={{action (mut this.showDrainUpdateNotification) false}} 116 type="button" 117 > 118 Okay 119 </button> 120 </div> 121 </div> 122 </div> 123 {{/if}} 124 {{#if this.showDrainNotification}} 125 <div class="notification is-info"> 126 <div data-test-drain-complete-notification class="columns"> 127 <div class="column"> 128 <h3 data-test-title class="title is-4"> 129 Drain Complete 130 </h3> 131 <p data-test-message> 132 Allocations have been drained and the node has been set to ineligible. 133 </p> 134 </div> 135 <div class="column is-centered is-minimum"> 136 <button 137 data-test-dimiss 138 class="button is-info" 139 onclick={{action (mut this.showDrainNotification) false}} 140 type="button" 141 > 142 Okay 143 </button> 144 </div> 145 </div> 146 </div> 147 {{/if}} 148 <div class="toolbar"> 149 <div class="toolbar-item is-top-aligned is-minimum"> 150 <span class="title"> 151 <span 152 data-test-node-status="{{this.model.compositeStatus}}" 153 class="node-status-light {{this.model.compositeStatus}}" 154 > 155 {{x-icon this.model.compositeStatusIcon}} 156 </span> 157 </span> 158 </div> 159 <div class="toolbar-item"> 160 <h1 data-test-title class="title with-subheading"> 161 {{or this.model.name this.model.shortId}} 162 </h1> 163 <p> 164 <label class="is-interactive"> 165 <Toggle 166 data-test-eligibility-toggle 167 @isActive={{this.model.isEligible}} 168 @isDisabled={{or 169 this.setEligibility.isRunning 170 this.model.isDraining 171 (cannot "write client") 172 }} 173 @onToggle={{perform this.setEligibility (not this.model.isEligible) 174 }} 175 > 176 Eligible 177 </Toggle> 178 <span 179 class="tooltip" 180 aria-label="Only eligible clients can receive allocations" 181 > 182 {{x-icon "info-circle-outline" class="is-faded"}} 183 </span> 184 </label> 185 <span 186 data-test-node-id 187 class="tag is-hollow is-small no-text-transform" 188 > 189 {{this.model.id}} 190 <CopyButton @clipboardText={{this.model.id}} /> 191 </span> 192 </p> 193 </div> 194 <div class="toolbar-item is-right-aligned is-top-aligned"> 195 {{#if this.model.isDraining}} 196 <TwoStepButton 197 data-test-drain-stop 198 @idleText="Stop Drain" 199 @cancelText="Cancel" 200 @confirmText="Yes, Stop Drain" 201 @confirmationMessage="Are you sure you want to stop this drain?" 202 @awaitingConfirmation={{this.stopDrain.isRunning}} 203 @onConfirm={{perform this.stopDrain}} 204 /> 205 {{/if}} 206 </div> 207 <div class="toolbar-item is-right-aligned is-top-aligned"> 208 <DrainPopover 209 @client={{this.model}} 210 @isDisabled={{cannot "write client"}} 211 @onDrain={{action "drainNotify"}} 212 @onError={{action "setDrainError"}} 213 /> 214 </div> 215 </div> 216 <div class="boxed-section is-small"> 217 <div class="boxed-section-body inline-definitions"> 218 <span class="label"> 219 Client Details 220 </span> 221 <span class="pair" data-test-status-definition> 222 <span class="term"> 223 Status 224 </span> 225 <span class="status-text node-{{this.model.status}}"> 226 {{this.model.status}} 227 </span> 228 </span> 229 <span class="pair" data-test-address-definition> 230 <span class="term"> 231 Address 232 </span> 233 {{this.model.httpAddr}} 234 </span> 235 <span class="pair" data-test-datacenter-definition> 236 <span class="term"> 237 Datacenter 238 </span> 239 {{this.model.datacenter}} 240 </span> 241 {{#if this.model.nodeClass}} 242 <span class="pair" data-test-node-class> 243 <span class="term"> 244 Class 245 </span> 246 {{this.model.nodeClass}} 247 </span> 248 {{/if}} 249 <span class="pair" data-test-driver-health> 250 <span class="term"> 251 Drivers 252 </span> 253 {{#if this.model.unhealthyDrivers.length}} 254 {{x-icon "alert-triangle" class="is-text is-warning"}} 255 {{this.model.unhealthyDrivers.length}} 256 of 257 {{this.model.detectedDrivers.length}} 258 {{pluralize "driver" this.model.detectedDrivers.length}} 259 unhealthy 260 {{else}} 261 All healthy 262 {{/if}} 263 </span> 264 </div> 265 </div> 266 {{#if this.model.drainStrategy}} 267 <div data-test-drain-details class="boxed-section is-info"> 268 <div class="boxed-section-head"> 269 <div class="boxed-section-row"> 270 Drain Strategy 271 </div> 272 <div class="boxed-section-row"> 273 <div class="inline-definitions is-small"> 274 {{#unless this.model.drainStrategy.hasNoDeadline}} 275 <span class="pair"> 276 <span class="term"> 277 Duration 278 </span> 279 {{#if this.model.drainStrategy.isForced}} 280 <span data-test-duration> 281 -- 282 </span> 283 {{else}} 284 <span 285 data-test-duration 286 class="tooltip" 287 aria-label={{format-duration 288 this.model.drainStrategy.deadline 289 }} 290 > 291 {{format-duration this.model.drainStrategy.deadline}} 292 </span> 293 {{/if}} 294 </span> 295 {{/unless}} 296 <span class="pair"> 297 <span class="term"> 298 {{if 299 this.model.drainStrategy.hasNoDeadline 300 "Deadline" 301 "Remaining" 302 }} 303 </span> 304 {{#if this.model.drainStrategy.hasNoDeadline}} 305 <span data-test-deadline> 306 No deadline 307 </span> 308 {{else if this.model.drainStrategy.isForced}} 309 <span data-test-deadline> 310 -- 311 </span> 312 {{else}} 313 <span 314 data-test-deadline 315 class="tooltip" 316 aria-label={{format-ts this.model.drainStrategy.forceDeadline 317 }} 318 > 319 {{moment-from-now 320 this.model.drainStrategy.forceDeadline 321 interval=1000 322 hideAffix=true 323 }} 324 </span> 325 {{/if}} 326 </span> 327 <span data-test-force-drain-text class="pair"> 328 <span class="term"> 329 Force Drain 330 </span> 331 {{#if this.model.drainStrategy.isForced}} 332 {{x-icon "alert-triangle" class="is-text is-warning"}}Yes 333 {{else}} 334 No 335 {{/if}} 336 </span> 337 <span data-test-drain-system-jobs-text class="pair"> 338 <span class="term"> 339 Drain System Jobs 340 </span> 341 {{if this.model.drainStrategy.ignoreSystemJobs "No" "Yes"}} 342 </span> 343 </div> 344 {{#unless this.model.drainStrategy.isForced}} 345 <div class="pull-right"> 346 <TwoStepButton 347 data-test-force 348 @alignRight={{true}} 349 @classes={{hash 350 idleButton="is-warning" 351 confirmationMessage="inherit-color" 352 cancelButton="is-danger is-important" 353 confirmButton="is-warning" 354 }} 355 @idleText="Force Drain" 356 @cancelText="Cancel" 357 @confirmText="Yes, Force Drain" 358 @confirmationMessage="Are you sure you want to force drain?" 359 @awaitingConfirmation={{this.forceDrain.isRunning}} 360 @onConfirm={{perform this.forceDrain}} 361 /> 362 </div> 363 {{/unless}} 364 </div> 365 </div> 366 <div class="boxed-section-body"> 367 <div class="columns"> 368 <div class="column nowrap is-minimum"> 369 <div class="metric-group"> 370 <div class="metric is-primary"> 371 <h3 class="label"> 372 Complete 373 </h3> 374 <p data-test-complete-count class="value"> 375 {{this.model.completeAllocations.length}} 376 </p> 377 </div> 378 </div> 379 <div class="metric-group"> 380 <div class="metric"> 381 <h3 class="label"> 382 Migrating 383 </h3> 384 <p data-test-migrating-count class="value"> 385 {{this.model.migratingAllocations.length}} 386 </p> 387 </div> 388 </div> 389 <div class="metric-group"> 390 <div class="metric"> 391 <h3 class="label"> 392 Remaining 393 </h3> 394 <p data-test-remaining-count class="value"> 395 {{this.model.runningAllocations.length}} 396 </p> 397 </div> 398 </div> 399 </div> 400 <div class="column"> 401 <h3 class="title is-4"> 402 Status 403 </h3> 404 {{#if this.model.lastMigrateTime}} 405 <p data-test-status> 406 {{moment-to-now 407 this.model.lastMigrateTime 408 interval=1000 409 hideAffix=true 410 }} 411 since an allocation was successfully migrated. 412 </p> 413 {{else}} 414 <p data-test-status> 415 No allocations migrated. 416 </p> 417 {{/if}} 418 </div> 419 </div> 420 </div> 421 </div> 422 {{/if}} 423 <div class="boxed-section"> 424 <div class="boxed-section-head is-hollow"> 425 Host Resource Utilization 426 <span 427 class="tooltip multiline pad-left" 428 aria-label="All allocation and system processes aggregated" 429 > 430 {{x-icon "info-circle-outline" class="is-faded"}} 431 </span> 432 </div> 433 <div class="boxed-section-body"> 434 <div class="columns"> 435 <div class="column"> 436 <PrimaryMetric::Node @node={{this.model}} @metric="cpu" /> 437 </div> 438 <div class="column"> 439 <PrimaryMetric::Node @node={{this.model}} @metric="memory" /> 440 </div> 441 </div> 442 </div> 443 </div> 444 <div class="boxed-section"> 445 <div class="boxed-section-head"> 446 <div> 447 Allocations 448 <button 449 role="button" 450 class="badge is-white" 451 onclick={{action "setPreemptionFilter" false}} 452 data-test-filter-all 453 type="button" 454 > 455 {{this.model.allocations.length}} 456 </button> 457 {{#if this.preemptions.length}} 458 <button 459 role="button" 460 class="badge is-warning" 461 onclick={{action "setPreemptionFilter" true}} 462 data-test-filter-preemptions 463 type="button" 464 > 465 {{this.preemptions.length}} 466 {{pluralize "preemption" this.preemptions.length}} 467 </button> 468 {{/if}} 469 </div> 470 <div class="pull-right is-subsection"> 471 <MultiSelectDropdown 472 data-test-allocation-namespace-facet 473 @label="Namespace" 474 @options={{this.optionsNamespace}} 475 @selection={{this.selectionNamespace}} 476 @onSelect={{action this.setFacetQueryParam "qpNamespace"}} 477 /> 478 <MultiSelectDropdown 479 data-test-allocation-job-facet 480 @label="Job" 481 @options={{this.optionsJob}} 482 @selection={{this.selectionJob}} 483 @onSelect={{action this.setFacetQueryParam "qpJob"}} 484 /> 485 <MultiSelectDropdown 486 data-test-allocation-status-facet 487 @label="Status" 488 @options={{this.optionsAllocationStatus}} 489 @selection={{this.selectionStatus}} 490 @onSelect={{action this.setFacetQueryParam "qpStatus"}} 491 /> 492 <SearchBox 493 @searchTerm={{mut this.searchTerm}} 494 @onChange={{action this.resetPagination}} 495 @placeholder="Search allocations..." 496 @inputClass="is-compact" 497 @class="is-padded" 498 /> 499 500 <span class="is-padded is-one-line"> 501 <Toggle 502 @isActive={{this.showSubTasks}} 503 @onToggle={{this.toggleShowSubTasks}} 504 title="Show tasks of allocations" 505 > 506 Show Tasks 507 </Toggle> 508 </span> 509 </div> 510 </div> 511 <div 512 class="boxed-section-body 513 {{if this.sortedAllocations.length "is-full-bleed"}}" 514 > 515 {{#if this.sortedAllocations.length}} 516 <ListPagination 517 @source={{this.sortedAllocations}} 518 @size={{this.pageSize}} 519 @page={{this.currentPage}} as |p| 520 > 521 <ListTable 522 @source={{p.list}} 523 @sortProperty={{this.sortProperty}} 524 @sortDescending={{this.sortDescending}} 525 @class="with-foot {{if this.showSubTasks "with-collapsed-borders"}}" as |t| 526 > 527 <t.head> 528 <th class="is-narrow"></th> 529 <t.sort-by @prop="shortId"> 530 ID 531 </t.sort-by> 532 <t.sort-by @prop="createIndex" @title="Create Index"> 533 Created 534 </t.sort-by> 535 <t.sort-by @prop="modifyIndex" @title="Modify Index"> 536 Modified 537 </t.sort-by> 538 <t.sort-by @prop="statusIndex"> 539 Status 540 </t.sort-by> 541 <t.sort-by @prop="job.name"> 542 Job 543 </t.sort-by> 544 <t.sort-by @prop="jobVersion"> 545 Version 546 </t.sort-by> 547 <th> 548 Volume 549 </th> 550 <th> 551 CPU 552 </th> 553 <th> 554 Memory 555 </th> 556 </t.head> 557 <t.body as |row|> 558 <AllocationRow 559 {{keyboard-shortcut 560 enumerated=true 561 action=(action "gotoAllocation" row.model) 562 }} 563 @allocation={{row.model}} 564 @context="node" 565 @onClick={{action "gotoAllocation" row.model}} 566 @data-test-allocation={{row.model.id}} 567 /> 568 {{#if this.showSubTasks}} 569 {{#each row.model.states as |task|}} 570 <TaskSubRow @namespan="8" @taskState={{task}} @active={{eq this.activeTask (concat task.allocation.id "-" task.name)}} @onSetActiveTask={{action 'setActiveTaskQueryParam'}} /> 571 {{/each}} 572 {{/if}} 573 </t.body> 574 </ListTable> 575 <div class="table-foot"> 576 <nav class="pagination"> 577 <div class="pagination-numbers"> 578 {{p.startsAt}} 579 – 580 {{p.endsAt}} 581 of 582 {{this.sortedAllocations.length}} 583 </div> 584 <p.prev @class="pagination-previous"> 585 < 586 </p.prev> 587 <p.next @class="pagination-next"> 588 > 589 </p.next> 590 <ul class="pagination-list"></ul> 591 </nav> 592 </div> 593 </ListPagination> 594 {{else}} 595 <div data-test-empty-allocations-list class="empty-message"> 596 {{#if (eq this.visibleAllocations.length 0)}} 597 <h3 598 data-test-empty-allocations-list-headline 599 class="empty-message-headline" 600 > 601 No Allocations 602 </h3> 603 <p data-test-empty-allocations-list-body class="empty-message-body"> 604 The node doesn't have any allocations. 605 </p> 606 {{else if this.searchTerm}} 607 <h3 608 data-test-empty-allocations-list-headline 609 class="empty-message-headline" 610 > 611 No Matches 612 </h3> 613 <p class="empty-message-body"> 614 No allocations match the term 615 <strong> 616 {{this.searchTerm}} 617 </strong> 618 </p> 619 {{else if (eq this.sortedAllocations.length 0)}} 620 <h3 621 data-test-empty-allocations-list-headline 622 class="empty-message-headline" 623 > 624 No Matches 625 </h3> 626 <p class="empty-message-body"> 627 No allocations match your current filter selection. 628 </p> 629 {{/if}} 630 </div> 631 {{/if}} 632 </div> 633 </div> 634 <div data-test-client-events class="boxed-section"> 635 <div class="boxed-section-head"> 636 Client Events 637 </div> 638 <div class="boxed-section-body is-full-bleed"> 639 <ListTable @source={{this.sortedEvents}} @class="is-striped" as |t|> 640 <t.head> 641 <th class="is-2"> 642 Time 643 </th> 644 <th class="is-2"> 645 Subsystem 646 </th> 647 <th> 648 Message 649 </th> 650 </t.head> 651 <t.body as |row|> 652 <tr data-test-client-event> 653 <td data-test-client-event-time> 654 {{format-ts row.model.time}} 655 </td> 656 <td data-test-client-event-subsystem> 657 {{row.model.subsystem}} 658 </td> 659 <td data-test-client-event-message> 660 {{#if row.model.message}} 661 {{#if row.model.driver}} 662 <span class="badge is-secondary is-small"> 663 {{row.model.driver}} 664 </span> 665 {{/if}} 666 {{row.model.message}} 667 {{else}} 668 <em> 669 No message 670 </em> 671 {{/if}} 672 </td> 673 </tr> 674 </t.body> 675 </ListTable> 676 </div> 677 </div> 678 {{#if this.sortedHostVolumes.length}} 679 <div data-test-client-host-volumes class="boxed-section"> 680 <div class="boxed-section-head"> 681 Host Volumes 682 </div> 683 <div class="boxed-section-body is-full-bleed"> 684 <ListTable 685 @source={{this.sortedHostVolumes}} 686 @class="is-striped" as |t| 687 > 688 <t.head> 689 <th> 690 Name 691 </th> 692 <th> 693 Source 694 </th> 695 <th> 696 Permissions 697 </th> 698 </t.head> 699 <t.body as |row|> 700 <tr data-test-client-host-volume> 701 <td data-test-name> 702 {{row.model.name}} 703 </td> 704 <td data-test-path> 705 <code> 706 {{row.model.path}} 707 </code> 708 </td> 709 <td data-test-permissions> 710 {{if row.model.readOnly "Read" "Read/Write"}} 711 </td> 712 </tr> 713 </t.body> 714 </ListTable> 715 </div> 716 </div> 717 {{/if}} 718 <div data-test-driver-status class="boxed-section"> 719 <div class="boxed-section-head"> 720 Driver Status 721 </div> 722 <div class="boxed-section-body"> 723 <ListAccordion @source={{this.sortedDrivers}} @key="name" as |a|> 724 <a.head 725 @buttonLabel="details" 726 @buttonType={{"client-detail"}} 727 @isExpandable={{a.item.detected}} 728 > 729 <div 730 class="columns inline-definitions 731 {{unless a.item.detected "is-faded"}}" 732 > 733 <div class="column is-1"> 734 <span data-test-name> 735 {{a.item.name}} 736 </span> 737 </div> 738 <div class="column is-2"> 739 {{#if a.item.detected}} 740 <span data-test-health> 741 <span class="color-swatch {{a.item.healthClass}}"></span> 742 {{if a.item.healthy "Healthy" "Unhealthy"}} 743 </span> 744 {{/if}} 745 </div> 746 <div class="column"> 747 <span class="pair"> 748 <span class="term"> 749 Detected 750 </span> 751 <span data-test-detected> 752 {{if a.item.detected "Yes" "No"}} 753 </span> 754 </span> 755 <span class="is-pulled-right"> 756 <span class="pair"> 757 <span class="term"> 758 Last Updated 759 </span> 760 <span 761 data-test-last-updated 762 class="tooltip" 763 aria-label="{{format-ts a.item.updateTime}}" 764 > 765 {{moment-from-now a.item.updateTime interval=1000}} 766 </span> 767 </span> 768 </span> 769 </div> 770 </div> 771 </a.head> 772 <a.body> 773 <p data-test-health-description class="message"> 774 {{a.item.healthDescription}} 775 </p> 776 <div data-test-driver-attributes class="boxed-section"> 777 <div class="boxed-section-head"> 778 {{capitalize a.item.name}} 779 Attributes 780 </div> 781 {{#if a.item.attributes.structured}} 782 <div class="boxed-section-body is-full-bleed"> 783 <AttributesTable 784 @attributePairs={{a.item.attributesShort}} 785 @class="attributes-table" 786 /> 787 </div> 788 {{else}} 789 <div class="boxed-section-body"> 790 <div class="empty-message"> 791 <h3 class="empty-message-headline"> 792 No Driver Attributes 793 </h3> 794 </div> 795 </div> 796 {{/if}} 797 </div> 798 </a.body> 799 </ListAccordion> 800 </div> 801 </div> 802 <div class="boxed-section"> 803 <div class="boxed-section-head"> 804 Attributes 805 </div> 806 <div class="boxed-section-body is-full-bleed"> 807 <AttributesTable 808 data-test-attributes 809 @attributePairs={{this.model.attributes.structured}} 810 @class="attributes-table" 811 /> 812 </div> 813 </div> 814 <div class="boxed-section"> 815 <div class="boxed-section-head"> 816 Meta 817 </div> 818 {{#if this.model.meta.structured}} 819 <div class="boxed-section-body is-full-bleed"> 820 <AttributesTable 821 data-test-meta 822 @attributePairs={{this.model.meta.structured}} 823 @class="attributes-table" 824 /> 825 </div> 826 {{else}} 827 <div class="boxed-section-body"> 828 <div data-test-empty-meta-message class="empty-message"> 829 <h3 class="empty-message-headline"> 830 No Meta Attributes 831 </h3> 832 <p class="empty-message-body"> 833 This client is configured with no meta attributes. 834 </p> 835 </div> 836 </div> 837 {{/if}} 838 </div> 839 </section>