Skip to content

Commit 6e1e7ea

Browse files
frostebiteclaude
andauthored
Add cleanup stuck builds button to versions page (#547)
* Add cleanup stuck builds button to versions page Adds an admin-only button to the versions page that calls the cleanUpStuckBuilds backend endpoint. This lets maintainers clean up all stuck builds directly from the UI with full context, rather than needing to trigger a GitHub workflow. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix lint: rename err to error Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Reject non-OK responses in useAuthenticatedEndpoint Previously, HTTP error responses (401, 500, etc.) resolved successfully since response.json() doesn't check status codes. Now throws an Error for non-OK responses so toast notifications correctly show the error path. Fixes both the cleanup button and the existing retry button. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 71d7118 commit 6e1e7ea

File tree

3 files changed

+67
-2
lines changed

3 files changed

+67
-2
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import React, { useState } from 'react';
2+
import { HiOutlineRefresh } from 'react-icons/hi';
3+
import { SimpleAuthCheck } from '@site/src/components/auth/safe-auth-check';
4+
import { useAuthenticatedEndpoint } from '@site/src/core/hooks/use-authenticated-endpoint';
5+
import { useNotification } from '@site/src/core/hooks/use-notification';
6+
import Spinner from '@site/src/components/molecules/spinner';
7+
import Tooltip from '@site/src/components/molecules/tooltip/tooltip';
8+
9+
const CleanUpStuckBuildsButton = () => {
10+
const [running, setRunning] = useState(false);
11+
const notify = useNotification();
12+
const cleanUp = useAuthenticatedEndpoint('cleanUpStuckBuilds', {});
13+
14+
const onClick = async () => {
15+
try {
16+
setRunning(true);
17+
await notify.promise(cleanUp(), {
18+
loading: <Spinner type="spin" />,
19+
success: (response) => {
20+
const results = response.results || [];
21+
return results.length > 0 ? results.join('\n') : response.message;
22+
},
23+
error: (error) => error.message || 'Cleanup failed',
24+
});
25+
} finally {
26+
setRunning(false);
27+
}
28+
};
29+
30+
return (
31+
<SimpleAuthCheck fallback={<span />} requiredClaims={{ admin: true }}>
32+
<Tooltip content="Clean up all stuck builds (checks DockerHub, marks as published or failed)">
33+
<button
34+
type="button"
35+
onClick={onClick}
36+
disabled={running}
37+
style={{
38+
padding: '4px 12px',
39+
border: '1px solid #ccc',
40+
borderRadius: 4,
41+
cursor: running ? 'wait' : 'pointer',
42+
background: 'transparent',
43+
display: 'inline-flex',
44+
alignItems: 'center',
45+
gap: 6,
46+
fontSize: '0.85em',
47+
}}
48+
>
49+
<HiOutlineRefresh color={running ? 'orange' : 'inherit'} />
50+
Clean up stuck builds
51+
</button>
52+
</Tooltip>
53+
</SimpleAuthCheck>
54+
);
55+
};
56+
57+
export default CleanUpStuckBuildsButton;

src/components/docs/versions/image-versions.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useState } from 'react';
22
import SignInSignOutButton from '@site/src/components/auth/sign-in-sign-out-button';
3+
import CleanUpStuckBuildsButton from './clean-up-stuck-builds-button';
34
import UnityVersions from './unity-versions';
45
import styles from './unity-version.module.scss';
56

@@ -28,7 +29,10 @@ const ImageVersions = ({ versions }: Props) => {
2829
);
2930
})}
3031
</select>
31-
<SignInSignOutButton style={{ float: 'right' }} />
32+
<span style={{ float: 'right', display: 'flex', alignItems: 'center', gap: 8 }}>
33+
<CleanUpStuckBuildsButton />
34+
<SignInSignOutButton />
35+
</span>
3236
</div>
3337

3438
<UnityVersions selectedRepoVersion={selectedVersion} />

src/core/hooks/use-authenticated-endpoint.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ export function useAuthenticatedEndpoint(endpoint: string, payload: { [key: stri
1616
body: JSON.stringify(payload),
1717
});
1818

19-
return response.json();
19+
const body = await response.json();
20+
if (!response.ok) {
21+
throw new Error(body.message || `Request failed (${response.status})`);
22+
}
23+
return body;
2024
};
2125
}

0 commit comments

Comments
 (0)