Skip to content

Commit 8e03224

Browse files
authored
ui: put templates first on the templates page and mark unbuilt ones (#1255)
* ui: put templates first on the templates page and mark unbuilt ones The templates page opened on the rolling warm-pool policy table, usually empty, had no page title, and listed the templates themselves third. A template with no published release (never built, or every build failed) showed "latest" in its Hash column, the same as a built template's tag. The Network column's tags and Edit link overflowed into the Base Snap toggle. The page now has a title with the refresh action, and lists templates, then releases, node availability and warm-pool policies. A template with no release shows "not built" with an explanation. The Hash and Network columns are wide enough for their contents. * ui: decide "no release" from the template releases The Hash column called a template "not built" when its content_hash was null. That field comes from builder inventory, not from releases, so a published template that no builder node reports showed "not built", and the rule was not what the previous commit's message described. A present template now shows "no release" only when the releases have loaded and none names it. The tooltip says it may never have been built, every build may have failed, or its releases were unpublished. A published template without a build hash shows a dash, with a tooltip saying it is published. Correction to the previous commit's message: it is not layout only. Its Hash column change is a behavior change, and its "not built" rule tested the build hash, not whether a release exists.
1 parent 46af5e3 commit 8e03224

1 file changed

Lines changed: 78 additions & 49 deletions

File tree

‎ui/src/app/dashboard/templates/page.tsx‎

Lines changed: 78 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,9 @@ export default function TemplatesPage() {
452452
const [warmPoolPolicies, setWarmPoolPolicies] = useState<WarmPoolPolicy[]>([]);
453453
const [warmPoolPolicyCapabilities, setWarmPoolPolicyCapabilities] = useState<WarmPoolPolicyCapabilities>({});
454454
const [loadingReleases, setLoadingReleases] = useState(true);
455+
// Whether `templateReleases` holds an answer yet: before it does, no
456+
// template can be said to have no release.
457+
const [releasesLoaded, setReleasesLoaded] = useState(false);
455458
const [loadingTemplates, setLoadingTemplates] = useState(true);
456459
const [loadingInventory, setLoadingInventory] = useState(true);
457460
const loadingRef = useRef(true);
@@ -533,6 +536,7 @@ export default function TemplatesPage() {
533536
try {
534537
const items = await adminApi.get<TemplateRelease[]>('/v1/template-releases');
535538
setTemplateReleases(items);
539+
setReleasesLoaded(true);
536540
return items;
537541
} catch (err: unknown) {
538542
console.warn(errorMessage(err));
@@ -1376,6 +1380,11 @@ export default function TemplatesPage() {
13761380
[templates],
13771381
);
13781382

1383+
const releasedTemplateNames = useMemo(
1384+
() => new Set(templateReleases.map((release) => release.template_name)),
1385+
[templateReleases],
1386+
);
1387+
13791388
const templateColumns = useMemo<ColumnsType<Template>>(() => {
13801389
const columns: ColumnsType<Template> = [
13811390
{
@@ -1393,12 +1402,30 @@ export default function TemplatesPage() {
13931402
{
13941403
title: 'Hash',
13951404
dataIndex: 'content_hash',
1396-
width: 110,
1397-
render: (value: string | null) => (
1398-
<Tooltip title={value ?? DEFAULT_TEMPLATE_TAG}>
1399-
<code>{shortHash(value)}</code>
1400-
</Tooltip>
1401-
),
1405+
width: 135,
1406+
// `content_hash` comes from builder inventory, not from releases:
1407+
// whether a template was ever published is read from the releases.
1408+
render: (value: string | null, record) => {
1409+
if (value) {
1410+
return (
1411+
<Tooltip title={value}>
1412+
<code>{shortHash(value)}</code>
1413+
</Tooltip>
1414+
);
1415+
}
1416+
if (releasesLoaded && record.desired_state === 'present' && !releasedTemplateNames.has(record.name)) {
1417+
return (
1418+
<Tooltip title="No release has been published for this template: it may never have been built, every build may have failed, or its releases were unpublished.">
1419+
<Tag style={{ marginInlineEnd: 0 }}>no release</Tag>
1420+
</Tooltip>
1421+
);
1422+
}
1423+
return releasedTemplateNames.has(record.name) ? (
1424+
<Tooltip title="Published; no builder node reports a build hash for it.">
1425+
<Text type="secondary">—</Text>
1426+
</Tooltip>
1427+
) : <Text type="secondary">—</Text>;
1428+
},
14021429
},
14031430
{
14041431
title: 'Runtime',
@@ -1452,9 +1479,9 @@ export default function TemplatesPage() {
14521479
{
14531480
title: 'Network',
14541481
dataIndex: 'network_policy',
1455-
width: 150,
1482+
width: 230,
14561483
render: (_, record) => (
1457-
<Space size={4}>
1484+
<Space size={4} wrap>
14581485
<Tag
14591486
color={compactNetworkPolicyLabel(record.network_policy) === 'open' ? 'green' : undefined}
14601487
style={{ marginRight: 0 }}
@@ -1548,6 +1575,8 @@ export default function TemplatesPage() {
15481575
return columns;
15491576
}, [
15501577
templateNameFilters,
1578+
releasedTemplateNames,
1579+
releasesLoaded,
15511580
baseSnapshotPolicies,
15521581
buildingSnapshot,
15531582
isDirectSubstrate,
@@ -1912,35 +1941,8 @@ export default function TemplatesPage() {
19121941
return (
19131942
<div className="compact-table">
19141943
<style>{`.compact-table .ant-table-cell { padding: 4px 6px !important; } .compact-table .icon-action-btn { padding-left: 6px; padding-right: 6px; } .compact-table .icon-action-btn .anticon { font-size: 18px; }`}</style>
1915-
{supportsClusterPools ? (
1916-
<>
1917-
<Space align="center" style={{ width: '100%', justifyContent: 'space-between' }}>
1918-
<div>
1919-
<Title level={4} style={{ marginBottom: 0 }}>Rolling Warm Pool Policies</Title>
1920-
<Text type="secondary">
1921-
Deployment-like policies create release-specific pools; the baseline is minimum ready slots multiplied by retained releases.
1922-
</Text>
1923-
</div>
1924-
{canAdminTemplates ? (
1925-
<Button type="primary" onClick={() => openWarmPoolPolicyModal()}>
1926-
New Policy
1927-
</Button>
1928-
) : null}
1929-
</Space>
1930-
<Table
1931-
columns={warmPoolPolicyColumns}
1932-
dataSource={warmPoolPolicies}
1933-
rowKey={(policy) => `${policy.template_name}:${policy.runtime}:${policy.vcpu}:${policy.memory_mib}`}
1934-
loading={loadingReleases}
1935-
size="small"
1936-
pagination={false}
1937-
style={{ marginTop: 12, marginBottom: 24 }}
1938-
scroll={{ x: 1080 }}
1939-
/>
1940-
</>
1941-
) : null}
1942-
<Space align="center" style={{ width: '100%', justifyContent: 'space-between' }}>
1943-
<Title level={4}>Template Releases</Title>
1944+
<Space align="center" style={{ width: '100%', justifyContent: 'space-between', marginBottom: 8 }}>
1945+
<Title level={3} style={{ margin: 0 }}>Templates</Title>
19441946
<Button
19451947
icon={<ReloadOutlined />}
19461948
loading={manualRefreshing}
@@ -1950,18 +1952,6 @@ export default function TemplatesPage() {
19501952
Refresh
19511953
</Button>
19521954
</Space>
1953-
1954-
<Table
1955-
columns={releaseColumns}
1956-
dataSource={templateReleases}
1957-
rowKey="id"
1958-
loading={loadingReleases}
1959-
size="small"
1960-
pagination={tablePagination('template releases')}
1961-
scroll={{ x: 1120 }}
1962-
/>
1963-
1964-
<Title level={4} style={{ marginTop: 24 }}>Templates</Title>
19651955
<Table
19661956
columns={templateColumns}
19671957
dataSource={templates}
@@ -1997,6 +1987,18 @@ export default function TemplatesPage() {
19971987
} : undefined}
19981988
/>
19991989

1990+
<Title level={4} style={{ marginTop: 24 }}>Template Releases</Title>
1991+
1992+
<Table
1993+
columns={releaseColumns}
1994+
dataSource={templateReleases}
1995+
rowKey="id"
1996+
loading={loadingReleases}
1997+
size="small"
1998+
pagination={tablePagination('template releases')}
1999+
scroll={{ x: 1120 }}
2000+
/>
2001+
20002002
<Title level={4} style={{ marginTop: 24 }}>Node Availability</Title>
20012003
<Table
20022004
columns={inventoryColumns}
@@ -2007,6 +2009,33 @@ export default function TemplatesPage() {
20072009
pagination={tablePagination('template builds')}
20082010
scroll={{ x: 1000 }}
20092011
/>
2012+
{supportsClusterPools ? (
2013+
<>
2014+
<Space align="center" style={{ width: '100%', justifyContent: 'space-between', marginTop: 24 }}>
2015+
<div>
2016+
<Title level={4} style={{ marginBottom: 0 }}>Rolling Warm Pool Policies</Title>
2017+
<Text type="secondary">
2018+
Deployment-like policies create release-specific pools; the baseline is minimum ready slots multiplied by retained releases.
2019+
</Text>
2020+
</div>
2021+
{canAdminTemplates ? (
2022+
<Button type="primary" onClick={() => openWarmPoolPolicyModal()}>
2023+
New Policy
2024+
</Button>
2025+
) : null}
2026+
</Space>
2027+
<Table
2028+
columns={warmPoolPolicyColumns}
2029+
dataSource={warmPoolPolicies}
2030+
rowKey={(policy) => `${policy.template_name}:${policy.runtime}:${policy.vcpu}:${policy.memory_mib}`}
2031+
loading={loadingReleases}
2032+
size="small"
2033+
pagination={false}
2034+
style={{ marginTop: 12, marginBottom: 24 }}
2035+
scroll={{ x: 1080 }}
2036+
/>
2037+
</>
2038+
) : null}
20102039

20112040
<Modal
20122041
title={warmPoolPolicyModal.policy

0 commit comments

Comments
 (0)