diff --git a/app/pages/system/UtilizationPage.tsx b/app/pages/system/UtilizationPage.tsx
index 6faaa82a1f..8a1e4a5675 100644
--- a/app/pages/system/UtilizationPage.tsx
+++ b/app/pages/system/UtilizationPage.tsx
@@ -24,6 +24,7 @@ import { QueryParamTabs } from '~/components/QueryParamTabs'
import { useIntervalPicker } from '~/components/RefetchIntervalPicker'
import { SystemMetric } from '~/components/SystemMetric'
import { LinkCell } from '~/table/cells/LinkCell'
+import { RowActions } from '~/table/columns/action-col'
import { Listbox } from '~/ui/lib/Listbox'
import { PageHeader, PageTitle } from '~/ui/lib/PageHeader'
import { ResourceMeter } from '~/ui/lib/ResourceMeter'
@@ -168,6 +169,7 @@ function UsageTab() {
Available
+
@@ -177,6 +179,7 @@ function UsageTab() {
CPU
Memory
Storage
+
@@ -225,6 +228,9 @@ function UsageTab() {
unit="TiB"
/>
+
+
+
))}
diff --git a/app/table/columns/action-col.tsx b/app/table/columns/action-col.tsx
index a130f15fdf..58a348cf04 100644
--- a/app/table/columns/action-col.tsx
+++ b/app/table/columns/action-col.tsx
@@ -50,54 +50,66 @@ export const getActionsCol = >(
// TODO: control flow here has always confused me, would like to straighten it out
const actions = makeActions(row.original)
const id = typeof row.original.id === 'string' ? row.original.id : null
- return (
-
- {/* TODO: This name should not suck; future us, make it so! */}
- {/* stopPropagation prevents clicks from toggling row select in a single select table */}
- e.stopPropagation()}
- >
-
-
- {/* portal fixes mysterious z-index issue where menu is behind button */}
-
-
- {id && (
+ return
+ },
+ }
+}
+
+type RowActionsProps = {
+ /** If `id` is provided, a `Copy ID` menu item will be automatically included. */
+ id?: string | null
+ /** Use `copyIdLabel` to override the default label (`Copy ID`). */
+ copyIdLabel?: string
+ actions?: MenuAction[]
+}
+
+export const RowActions = ({ id, copyIdLabel = 'Copy ID', actions }: RowActionsProps) => {
+ return (
+
+ {/* TODO: This name should not suck; future us, make it so! */}
+ {/* stopPropagation prevents clicks from toggling row select in a single select table */}
+ e.stopPropagation()}
+ >
+
+
+ {/* portal fixes mysterious z-index issue where menu is behind button */}
+
+
+ {id && (
+ {
+ window.navigator.clipboard.writeText(id)
+ }}
+ >
+ {copyIdLabel}
+
+ )}
+ {actions?.map((action) => {
+ // TODO: Tooltip on disabled button broke, probably due to portal
+ return (
+ }
+ key={kebabCase(`action-${action.label}`)}
+ >
{
- window.navigator.clipboard.writeText(id)
- }}
+ className={cn(action.className, {
+ destructive:
+ action.label.toLowerCase() === 'delete' && !action.disabled,
+ })}
+ onSelect={action.onActivate}
+ disabled={!!action.disabled}
>
- Copy ID
+ {action.label}
- )}
- {actions.map((action) => {
- // TODO: Tooltip on disabled button broke, probably due to portal
- return (
- }
- key={kebabCase(`action-${action.label}`)}
- >
-
- {action.label}
-
-
- )
- })}
-
-
-
- )
- },
- }
+
+ )
+ })}
+
+
+
+ )
}
diff --git a/test/e2e/images.e2e.ts b/test/e2e/images.e2e.ts
index 0c34aae0b8..0c15a42811 100644
--- a/test/e2e/images.e2e.ts
+++ b/test/e2e/images.e2e.ts
@@ -77,8 +77,8 @@ test('can promote an image from project', async ({ page }) => {
test('can copy an image ID to clipboard', async ({ page, browserName }) => {
// eslint-disable-next-line playwright/no-skipped-test
test.skip(
- browserName === 'firefox' || browserName === 'webkit',
- 'navigator.clipboard.readText() not supported in FF. Works locally in Safari but not in CI.'
+ browserName === 'webkit',
+ 'navigator.clipboard.readText() works locally in Safari but not in CI.'
)
await page.goto('/images')
diff --git a/test/e2e/utilization.e2e.ts b/test/e2e/utilization.e2e.ts
index 187cea6a12..f37b1a17ed 100644
--- a/test/e2e/utilization.e2e.ts
+++ b/test/e2e/utilization.e2e.ts
@@ -7,6 +7,7 @@
*/
import {
clickRowAction,
+ clipboardText,
closeToast,
expect,
expectRowVisible,
@@ -43,6 +44,17 @@ test.describe('System utilization', () => {
await page.getByRole('tab', { name: 'Metrics' }).click()
})
+ test('can copy silo ID', async ({ page, browserName }) => {
+ // eslint-disable-next-line playwright/no-skipped-test
+ test.skip(
+ browserName === 'webkit',
+ 'navigator.clipboard.readText() works locally in Safari but not in CI.'
+ )
+ await page.goto('/system/utilization')
+ await clickRowAction(page, 'maze-war', 'Copy silo ID')
+ expect(await clipboardText(page)).toEqual('6d3a9c06-475e-4f75-b272-c0d0e3f980fa')
+ })
+
test('does not appear for dev user', async ({ browser }) => {
const page = await getPageAsUser(browser, 'Hans Jonas')
await page.goto('/system/utilization')