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