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                  &lt;
   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>