open-nomad/ui/app/templates/topology.hbs
Phil Renaud 54eeb6ebe8
Adds searching and filtering for nodes on topology view (#14913)
* Adds searching and filtering for nodes on topology view

* Lintfix and changelog

* Acceptance tests for topology search and filter

* Search terms also apply to class and dc on topo page

* Initialize queryparam values so as to not break history state
2022-10-19 15:00:35 -04:00

524 lines
20 KiB
Handlebars

<Breadcrumb @crumb={{hash label="Topology" args=(array "topology")}} />
{{page-title "Cluster Topology"}}
<PageLayout>
<section class="section is-full-width">
{{#if this.isForbidden}}
<ForbiddenMessage />
{{else}}
{{#if this.pre09Nodes}}
<div class="notification is-warning">
<div data-test-filtered-nodes-warning class="columns">
<div class="column">
<h3 data-test-title class="title is-4">
Some Clients Were Filtered
</h3>
<p data-test-message>
{{this.pre09Nodes.length}}
{{if (eq this.pre09Nodes.length 1) "client was" "clients were"}}
filtered from the topology visualization. This is most likely due to the
{{pluralize "client" this.pre09Nodes.length}}
running a version of Nomad
</p>
</div>
<div class="column is-centered is-minimum">
<button
data-test-dismiss
class="button is-warning"
onclick={{action (mut this.pre09Nodes) null}}
type="button"
>
Okay
</button>
</div>
</div>
</div>
{{/if}}
<div class="columns">
<div class="column is-narrow is-400">
{{#if this.showPollingNotice}}
<div class="notification is-warning">
<div class="columns">
<div class="column">
<h3 class="title is-4">
No Live Updating
</h3>
<p>
The topology visualization depends on too much data to continuously poll.
</p>
</div>
<div class="column is-centered is-minimum">
<button
data-test-polling-notice-dismiss
class="button is-warning"
type="button"
onclick={{toggle-action "showPollingNotice" this}}
>
Okay
</button>
</div>
</div>
</div>
{{/if}}
<div class="boxed-section">
<div class="boxed-section-head">
Legend
{{#if (cannot "list all jobs")}}
<span
aria-label="Your ACL token may limit your ability to list all allocations"
class="tag is-warning pull-right tooltip multiline"
>
Partial View
</span>
{{/if}}
</div>
<div class="boxed-section-body">
<div class="legend">
<h3 class="legend-label">
Metrics
</h3>
<dl class="legend-terms">
<dt>
M:
</dt>
<dd>
Memory
</dd>
<dt>
C:
</dt>
<dd>
CPU
</dd>
</dl>
</div>
<div class="legend">
<h3 class="legend-label">
Allocation Status
</h3>
<dl class="legend-terms">
<div class="legend-term">
<dt>
<span class="color-swatch is-wide running" title="Running"></span>
</dt>
<dd>
Running
</dd>
</div>
<div class="legend-term">
<dt>
<span class="color-swatch is-wide pending" title="Starting"></span>
</dt>
<dd>
Starting
</dd>
</div>
</dl>
</div>
</div>
</div>
<div class="boxed-section">
<div data-test-info-panel-title class="boxed-section-head">
{{#if this.activeNode}}
Client
{{else if this.activeAllocation}}
Allocation
{{else}}
Cluster
{{/if}}
Details
</div>
<div data-test-info-panel class="boxed-section-body">
{{#if this.activeNode}}
{{#let this.activeNode.node as |node|}}
<div class="dashboard-metric">
<p data-test-allocations class="metric">
{{this.activeNode.allocations.length}}
<span class="metric-label">
Allocations
</span>
</p>
</div>
<div class="dashboard-metric">
<h3 class="pair">
<strong>
Client:
</strong>
<LinkTo data-test-client-link @route="clients.client" @model={{node}}>
{{node.shortId}}
</LinkTo>
</h3>
<p data-test-name class="minor-pair">
<strong>
Name:
</strong>
{{node.name}}
</p>
<p data-test-address class="minor-pair">
<strong>
Address:
</strong>
{{node.httpAddr}}
</p>
<p data-test-status class="minor-pair">
<strong>
Status:
</strong>
{{node.status}}
</p>
</div>
<div class="dashboard-metric">
<h3 class="pair">
<strong>
Draining?
</strong>
<span data-test-draining class="{{if node.isDraining "status-text is-info"}}">
{{if node.isDraining "Yes" "No"}}
</span>
</h3>
<h3 class="pair">
<strong>
Eligible?
</strong>
<span
data-test-eligible
class="{{unless node.isEligible "status-text is-warning"}}"
>
{{if node.isEligible "Yes" "No"}}
</span>
</h3>
</div>
<div class="dashboard-metric with-divider">
<p class="metric">
{{this.nodeUtilization.totalMemoryFormatted}}
<span class="metric-units">
{{this.nodeUtilization.totalMemoryUnits}}
</span>
<span class="metric-label">
of memory
</span>
</p>
<div class="columns graphic">
<div class="column">
<div class="inline-chart">
<progress
data-test-memory-progress-bar
class="progress is-danger is-small"
value="{{this.nodeUtilization.reservedMemoryPercent}}"
max="1"
>
{{this.nodeUtilization.reservedMemoryPercent}}
</progress>
</div>
</div>
<div class="column is-minimum">
<span class="nowrap" data-test-percentage>
{{format-percentage this.nodeUtilization.reservedMemoryPercent total=1}}
</span>
</div>
</div>
<div class="annotation" data-test-memory-absolute-value>
<strong>
{{format-scheduled-bytes this.nodeUtilization.totalReservedMemory}}
</strong>
/
{{format-scheduled-bytes this.nodeUtilization.totalMemory}}
reserved
</div>
</div>
<div class="dashboard-metric">
<p class="metric">
{{this.nodeUtilization.totalCPU}}
<span class="metric-units">
MHz
</span>
<span class="metric-label">
of CPU
</span>
</p>
<div class="columns graphic">
<div class="column">
<div class="inline-chart" data-test-percentage-bar>
<progress
data-test-cpu-progress-bar
class="progress is-info is-small"
value="{{this.nodeUtilization.reservedCPUPercent}}"
max="1"
>
{{this.nodeUtilization.reservedCPUPercent}}
</progress>
</div>
</div>
<div class="column is-minimum">
<span class="nowrap" data-test-percentage>
{{format-percentage this.nodeUtilization.reservedCPUPercent total=1}}
</span>
</div>
</div>
<div class="annotation" data-test-cpu-absolute-value>
<strong>
{{format-scheduled-hertz this.nodeUtilization.totalReservedCPU}}
</strong>
/
{{format-scheduled-hertz this.nodeUtilization.totalCPU}}
reserved
</div>
</div>
{{/let}}
{{else if this.activeAllocation}}
<div class="dashboard-metric">
<h3 class="pair">
<strong>
Allocation:
</strong>
<LinkTo
data-test-id
@route="allocations.allocation"
@model={{this.activeAllocation}}
class="is-primary"
>
{{this.activeAllocation.shortId}}
</LinkTo>
</h3>
<p data-test-sibling-allocs class="minor-pair">
<strong>
Sibling Allocations:
</strong>
{{this.siblingAllocations.length}}
</p>
<p data-test-unique-placements class="minor-pair">
<strong>
Unique Client Placements:
</strong>
{{this.uniqueActiveAllocationNodes.length}}
</p>
</div>
<div class="dashboard-metric with-divider">
<h3 class="pair">
<strong>
Job:
</strong>
<LinkTo
data-test-job
@route="jobs.job"
@model={{this.activeAllocation.job}}
>
{{this.activeAllocation.job.name}}
</LinkTo>
<span class="is-faded" data-test-task-group>
/
{{this.activeAllocation.taskGroupName}}
</span>
</h3>
<p class="minor-pair">
<strong>
Type:
</strong>
{{this.activeAllocation.job.type}}
</p>
<p class="minor-pair">
<strong>
Priority:
</strong>
{{this.activeAllocation.job.priority}}
</p>
</div>
<div class="dashboard-metric with-divider">
<h3 class="pair">
<strong>
Client:
</strong>
<LinkTo
data-test-client
@route="clients.client"
@model={{this.activeAllocation.node}}
>
{{this.activeAllocation.node.shortId}}
</LinkTo>
</h3>
<p class="minor-pair">
<strong>
Name:
</strong>
{{this.activeAllocation.node.name}}
</p>
<p class="minor-pair">
<strong>
Address:
</strong>
{{this.activeAllocation.node.httpAddr}}
</p>
</div>
<div class="dashboard-metric with-divider">
<PrimaryMetric::Allocation
@allocation={{this.activeAllocation}}
@metric="memory"
class="is-short"
/>
</div>
<div class="dashboard-metric">
<PrimaryMetric::Allocation
@allocation={{this.activeAllocation}}
@metric="cpu"
class="is-short"
/>
</div>
{{else}}
<div class="columns is-flush">
<div class="dashboard-metric column">
<p data-test-node-count class="metric">
{{this.model.nodes.length}}
<span class="metric-label">
Clients
</span>
</p>
</div>
<div class="dashboard-metric column">
<p data-test-alloc-count class="metric">
{{this.scheduledAllocations.length}}
<span class="metric-label">
Allocations
</span>
</p>
</div>
</div>
<div class="dashboard-metric with-divider">
<p class="metric">
{{this.totalMemoryFormatted}}
<span class="metric-units">
{{this.totalMemoryUnits}}
</span>
<span class="metric-label">
of memory
</span>
</p>
<div class="columns graphic">
<div class="column">
<div class="inline-chart" data-test-percentage-bar>
<progress
data-test-memory-progress-bar
class="progress is-danger is-small"
value="{{this.reservedMemoryPercent}}"
max="1"
>
{{this.reservedMemoryPercent}}
</progress>
</div>
</div>
<div class="column is-minimum">
<span class="nowrap" data-test-memory-percentage>
{{format-percentage this.reservedMemoryPercent total=1}}
</span>
</div>
</div>
<div class="annotation" data-test-memory-absolute-value>
<strong>
{{format-bytes this.totalReservedMemory}}
</strong>
/
{{format-bytes this.totalMemory}}
reserved
</div>
</div>
<div class="dashboard-metric">
<p class="metric">
{{this.totalCPUFormatted}}
<span class="metric-units">
{{this.totalCPUUnits}}
</span>
<span class="metric-label">
of CPU
</span>
</p>
<div class="columns graphic">
<div class="column">
<div class="inline-chart" data-test-percentage-bar>
<progress
data-test-cpu-progress-bar
class="progress is-info is-small"
value="{{this.reservedCPUPercent}}"
max="1"
>
{{this.reservedCPUPercent}}
</progress>
</div>
</div>
<div class="column is-minimum">
<span class="nowrap" data-test-cpu-percentage>
{{format-percentage this.reservedCPUPercent total=1}}
</span>
</div>
</div>
<div class="annotation" data-test-cpu-absolute-value>
<strong>
{{format-hertz this.totalReservedCPU}}
</strong>
/
{{format-hertz this.totalCPU}}
reserved
</div>
</div>
{{/if}}
</div>
</div>
</div>
<div class="column">
<div class="toolbar">
<div class="toolbar-item">
{{#if this.model.nodes.length}}
<SearchBox
@inputClass="node-search"
@searchTerm={{mut this.searchTerm}}
@placeholder="Search clients..."
/>
{{/if}}
</div>
<div class="toolbar-item is-right-aligned is-mobile-full-width">
<div class="button-bar">
<MultiSelectDropdown
data-test-datacenter-facet
@label="Datacenter"
@options={{this.optionsDatacenter}}
@selection={{this.selectionDatacenter}}
@onSelect={{action this.setFacetQueryParam "qpDatacenter"}}
/>
<MultiSelectDropdown
data-test-class-facet
@label="Class"
@options={{this.optionsClass}}
@selection={{this.selectionClass}}
@onSelect={{action this.setFacetQueryParam "qpClass"}}
/>
<MultiSelectDropdown
data-test-state-facet
@label="State"
@options={{this.optionsState}}
@selection={{this.selectionState}}
@onSelect={{action this.setFacetQueryParam "qpState"}}
/>
<MultiSelectDropdown
data-test-version-facet
@label="Version"
@options={{this.optionsVersion}}
@selection={{this.selectionVersion}}
@onSelect={{action this.setFacetQueryParam "qpVersion"}}
/>
</div>
</div>
</div>
<TopoViz
@nodes={{this.filteredNodes}}
@allocations={{this.model.allocations}}
@onAllocationSelect={{action this.setAllocation}}
@onNodeSelect={{action this.setNode}}
@onDataError={{action this.handleTopoVizDataError}}
@filters={{hash
search=this.searchTerm
clientState=this.selectionState
clientVersion=this.selectionVersion
}}
/>
</div>
</div>
{{/if}}
</section>
</PageLayout>