Files
nomad/ui/app/templates/storage/index.hbs
Aimee Ukasick a30cb2f137 Update UI, code comment, and README links to docs, tutorials (#26429)
* Update UI, code comment, and README links to docs, tutorials

* fix typo in ephemeral disks learn more link url

* feedback on typo

Co-authored-by: Tim Gross <tgross@hashicorp.com>

---------

Co-authored-by: Tim Gross <tgross@hashicorp.com>
2025-08-06 09:40:23 -05:00

249 lines
9.9 KiB
Handlebars

{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
~}}
{{page-title "Storage"}}
<Breadcrumb @crumb={{hash label="Storage" args=(array "storage.index")}} />{{outlet}}
<StorageSubnav />
<section class="section storage-index">
<Hds::PageHeader @title="Storage" as |PH|>
<PH.Actions>
{{#if this.system.shouldShowNamespaces}}
<Hds::Dropdown data-test-namespace-facet as |dd|>
<dd.ToggleButton @text="Namespace ({{this.qpNamespace}})" @color="secondary" />
{{#each this.optionsNamespaces as |option|}}
<dd.Radio
name={{option.key}}
{{on "change" (action (mut this.qpNamespace) option.key)}}
checked={{eq this.qpNamespace option.key}}
>
{{option.label}}
</dd.Radio>
{{/each}}
</Hds::Dropdown>
{{/if}}
</PH.Actions>
</Hds::PageHeader>
{{#if this.isForbidden}}
<ForbiddenMessage />
{{else}}
<Hds::Card::Container @level="base" @hasBorder={{false}} class="storage-index-table-card" data-test-csi-volumes-card>
<header aria-label="CSI Volumes">
<h3>CSI Volumes</h3>
<p class="intro">
Storage configured by plugins run as Nomad jobs, with advanced features like snapshots and resizing.
<Hds::Link::Inline @href="https://developer.hashicorp.com/nomad/docs/other-specifications/volume/csi" @icon="docs-link" @iconPosition="trailing">Read more</Hds::Link::Inline>
</p>
<div class="search">
<Hds::Form::TextInput::Field
data-test-csi-volumes-search
@type="search"
@value={{this.csiFilter}}
placeholder="Search CSI Volumes"
{{on "input" (action this.applyFilter "csi")}}
/>
</div>
</header>
{{#if this.sortedCSIVolumes.length}}
<Hds::Table @caption="CSI Volumes"
@model={{this.paginatedCSIVolumes}}
@columns={{this.csiColumns}}
@sortBy={{this.csiSortProperty}}
@sortOrder={{if this.csiSortDescending "desc" "asc"}}
@onSort={{action this.handleSort "csi"}}
>
<:body as |B|>
<B.Tr data-test-csi-volume-row
{{keyboard-shortcut
enumerated=true
action=(action this.openCSI B.data)
}}
>
<B.Td data-test-csi-volume-name>
<LinkTo
@route="storage.volumes.volume"
@model={{B.data.idWithNamespace}}
class="is-primary"
>
{{B.data.plainId}}
</LinkTo>
</B.Td>
{{#if this.system.shouldShowNamespaces}}
<B.Td data-test-csi-volume-namespace>
{{B.data.namespace.name}}
</B.Td>
{{/if}}
<B.Td data-test-csi-volume-schedulable>
{{if B.data.schedulable "Schedulable" "Unschedulable"}}
</B.Td>
<B.Td data-test-csi-volume-controller-health>
{{#if B.data.controllerRequired}}
{{if (gt B.data.controllersHealthy 0) "Healthy" "Unhealthy"}}
(
{{B.data.controllersHealthy}}
/
{{B.data.controllersExpected}}
)
{{else if (gt B.data.controllersExpected 0)}}
{{if (gt B.data.controllersHealthy 0) "Healthy" "Unhealthy"}}
(
{{B.data.controllersHealthy}}
/
{{B.data.controllersExpected}}
)
{{else}}
<em class="is-faded">
Node Only
</em>
{{/if}}
</B.Td>
<B.Td data-test-csi-volume-node-health>
{{if (gt B.data.nodesHealthy 0) "Healthy" "Unhealthy"}}
(
{{B.data.nodesHealthy}}
/
{{B.data.nodesExpected}}
)
</B.Td>
<B.Td data-test-csi-volume-plugin>
<LinkTo @route="storage.plugins.plugin" @model={{B.data.plugin.plainId}}>
{{B.data.plugin.plainId}}
</LinkTo>
</B.Td>
<B.Td data-test-csi-volume-allocations>
{{B.data.allocationCount}}
</B.Td>
</B.Tr>
</:body>
</Hds::Table>
<Hds::Pagination::Numbered
@totalItems={{this.filteredCSIVolumes.length}}
@currentPage={{this.csiPage}}
@pageSizes={{this.pageSizes}}
@currentPageSize={{this.userSettings.pageSize}}
@onPageChange={{action this.handlePageChange "csi"}}
@onPageSizeChange={{action (mut this.userSettings.pageSize)}}
/>
{{else}}
<div class="empty-message" data-test-empty-csi-volumes-list-headline>
{{#if this.csiFilter}}
<p>No CSI volumes match your search for "{{this.csiFilter}}"</p>
<Hds::Button @text="Clear search" @color="secondary" {{on "click" (queue (action (mut this.csiFilter) "") (action this.handlePageChange "csi" 1))}} />
{{else}}
<p>No CSI Volumes found</p>
{{/if}}
</div>
{{/if}}
</Hds::Card::Container>
<Hds::Card::Container @level="base" @hasBorder={{false}} class="storage-index-table-card" data-test-dynamic-host-volumes-card>
<header aria-label="Dynamic Host Volumes">
<h3>Dynamic Host Volumes</h3>
<p class="intro">
Storage provisioned via plugin scripts on a particular client, modifiable without requiring client restart.
<Hds::Link::Inline @href="https://developer.hashicorp.com/nomad/docs/other-specifications/volume/host" @icon="docs-link" @iconPosition="trailing">Read more</Hds::Link::Inline>
</p>
<div class="search">
<Hds::Form::TextInput::Field
data-test-dynamic-host-volumes-search
@type="search"
@value={{this.dhvFilter}}
placeholder="Search Dynamic Host Volumes"
{{on "input" (action this.applyFilter "dhv")}}
/>
</div>
</header>
{{#if this.sortedDynamicHostVolumes.length}}
<Hds::Table @caption="Dynamic Host Volumes"
@model={{this.paginatedDynamicHostVolumes}}
@columns={{this.dhvColumns}}
@sortBy={{this.dhvSortProperty}}
@sortOrder={{if this.dhvSortDescending "desc" "asc"}}
@onSort={{action this.handleSort "dhv"}}
>
<:body as |B|>
<B.Tr data-test-dhv-row
{{keyboard-shortcut
enumerated=true
action=(action this.openDHV B.data)
}}
>
<B.Td>
<LinkTo data-test-dhv-name={{B.data.name}} @route="storage.volumes.dynamic-host-volume"
@model={{B.data.idWithNamespace}}>
{{B.data.plainId}}
</LinkTo>
</B.Td>
<B.Td>
{{B.data.name}}
</B.Td>
{{#if this.system.shouldShowNamespaces}}
<B.Td>{{B.data.namespace}}</B.Td>
{{/if}}
<B.Td>
<LinkTo @route="clients.client" @model={{B.data.node.id}}>
{{B.data.node.name}}
</LinkTo>
</B.Td>
<B.Td>{{B.data.pluginID}}</B.Td>
<B.Td>{{B.data.state}}</B.Td>
<B.Td>
<span class="tooltip" aria-label="{{format-month-ts B.data.modifyTime}}">
{{moment-from-now B.data.modifyTime}}
</span>
</B.Td>
</B.Tr>
</:body>
</Hds::Table>
<Hds::Pagination::Numbered
@totalItems={{this.filteredDynamicHostVolumes.length}}
@currentPage={{this.dhvPage}}
@pageSizes={{this.pageSizes}}
@currentPageSize={{this.userSettings.pageSize}}
@onPageChange={{action this.handlePageChange "dhv"}}
@onPageSizeChange={{action (mut this.userSettings.pageSize)}}
/>
{{else}}
<div class="empty-message" data-test-empty-dhv-list-headline>
{{#if this.dhvFilter}}
<p>No dynamic host volumes match your search for "{{this.dhvFilter}}"</p>
<Hds::Button @text="Clear search" @color="secondary" {{on "click" (queue (action (mut this.dhvFilter) "") (action this.handlePageChange "dhv" 1))}} />
{{else}}
<p>No Dynamic Host Volumes found</p>
{{/if}}
</div>
{{/if}}
</Hds::Card::Container>
<Hds::Card::Container @level="base" @hasBorder={{false}} class="info-panels storage-index-table-card">
<header aria-label="Other Storage Types">
<h3>Other Storage Types</h3>
</header>
<Hds::Alert @type="inline" @color="highlight" @icon="hard-drive" as |A|>
<A.Title>
Static Host Volumes
</A.Title>
<A.Description>
Defined in the Nomad agent's config file, best for infrequently changing storage
</A.Description>
<A.Button @color="secondary" @icon="arrow-right" @iconPosition="trailing" @text="Learn more" @href="https://developer.hashicorp.com/nomad/docs/stateful-workloads/static-host-volumes" />
</Hds::Alert>
<Hds::Alert @type="inline" @color="highlight" @icon="hard-drive" as |A|>
<A.Title>
Ephemeral Disks
</A.Title>
<A.Description>
Best-effort persistence, ideal for rebuildable data. Stored in the <code>/alloc/data</code> directory in a given allocation.
</A.Description>
<A.Button @color="secondary" @icon="arrow-right" @iconPosition="trailing" @text="Learn more" @href="https://developer.hashicorp.com/nomad/docs/architecture/storage/stateful-workloads#ephemeral-disks" />
</Hds::Alert>
</Hds::Card::Container>
{{/if}}
</section>