|
6 | 6 | * Copyright Oxide Computer Company |
7 | 7 | */ |
8 | 8 |
|
| 9 | +import { differenceInMinutes } from 'date-fns' |
9 | 10 | import { useMemo } from 'react' |
10 | 11 | import * as R from 'remeda' |
| 12 | +import { lt as semverLt } from 'semver' |
11 | 13 |
|
12 | 14 | import { |
13 | 15 | Images24Icon, |
@@ -46,7 +48,29 @@ import { percentage, round } from '~/util/math' |
46 | 48 |
|
47 | 49 | export const handle = makeCrumb('System Update') |
48 | 50 |
|
49 | | -const statusQuery = apiq('systemUpdateStatus', {}) |
| 51 | +const SEC = 1000 // ms, obviously |
| 52 | +const POLL_FAST = 20 * SEC |
| 53 | +const POLL_SLOW = 120 * SEC |
| 54 | + |
| 55 | +const statusQuery = apiq( |
| 56 | + 'systemUpdateStatus', |
| 57 | + {}, |
| 58 | + { |
| 59 | + refetchInterval({ state: { data: status } }) { |
| 60 | + if (!status) return false // should be impossible due to prefetch |
| 61 | + |
| 62 | + const now = new Date() |
| 63 | + const minSinceTargetSet = status.targetRelease |
| 64 | + ? differenceInMinutes(now, status.targetRelease.timeRequested) |
| 65 | + : null |
| 66 | + const minSinceLastStepPlanned = differenceInMinutes(now, status.timeLastStepPlanned) |
| 67 | + return minSinceLastStepPlanned < 30 || |
| 68 | + (minSinceTargetSet !== null && minSinceTargetSet < 30) |
| 69 | + ? POLL_FAST |
| 70 | + : POLL_SLOW |
| 71 | + }, |
| 72 | + } |
| 73 | +) |
50 | 74 | const reposQuery = apiq('systemUpdateRepositoryList', { query: { limit: ALL_ISH } }) |
51 | 75 |
|
52 | 76 | const refreshData = () => |
@@ -175,7 +199,17 @@ export default function UpdatePage() { |
175 | 199 | <CardBlock.Body> |
176 | 200 | <ul className="space-y-3"> |
177 | 201 | {repos.items.map((repo) => { |
178 | | - const isTarget = repo.systemVersion === status.targetRelease?.version |
| 202 | + const targetVersion = status.targetRelease?.version |
| 203 | + const isTarget = repo.systemVersion === targetVersion |
| 204 | + // semverLt looks at prerelease meta but not build meta. In prod |
| 205 | + // it doesn't matter either way because there will be neither. On |
| 206 | + // dogfood it shouldn't matter because the versions will usually |
| 207 | + // be the same and with the same prelease meta and only differing |
| 208 | + // build meta, so semverLt will return false and we don't disable |
| 209 | + // any. Very important this is |
| 210 | + const olderThanTarget = targetVersion |
| 211 | + ? semverLt(repo.systemVersion, targetVersion) |
| 212 | + : false |
179 | 213 | return ( |
180 | 214 | <li |
181 | 215 | key={repo.hash} |
@@ -226,10 +260,13 @@ export default function UpdatePage() { |
226 | 260 | errorTitle: `Error setting target release to ${repo.systemVersion}`, |
227 | 261 | }) |
228 | 262 | }} |
229 | | - // TODO: follow API logic, disabling for older releases. |
230 | | - // Or maybe just have the API tell us by adding a field to |
231 | | - // the TufRepo response type. |
232 | | - disabled={isTarget && 'Already set as target'} |
| 263 | + disabled={ |
| 264 | + isTarget |
| 265 | + ? 'Already set as target' |
| 266 | + : olderThanTarget |
| 267 | + ? 'Cannot set older release as target' |
| 268 | + : false |
| 269 | + } |
233 | 270 | /> |
234 | 271 | </MoreActionsMenu> |
235 | 272 | </div> |
|
0 commit comments