-
Notifications
You must be signed in to change notification settings - Fork 13
Silo quotas on silo detail #2369
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
9879d9e
mock silo quotas endpoints
david-crespo 2000ec8
display silo quotas in a tab on silo detail (read only)
david-crespo 96ca616
edit form w/ basic e2e test
david-crespo c8d882a
fix required validation on edit form, test negative number validation…
david-crespo 9534779
add provisioned column to table
david-crespo 8ac826f
put the button *under* the table. genius
david-crespo c3db0ca
Merge branch 'main' into silo-quotas
david-crespo 50cc6ad
Merge main into silo-quotas
david-crespo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,10 +47,6 @@ const defaultValues: SiloCreateFormValues = { | |
}, | ||
} | ||
|
||
function validateQuota(value: number) { | ||
if (value < 0) return 'Must be at least 0' | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is covered by the min = 0 on |
||
export function CreateSiloSideModalForm() { | ||
const navigate = useNavigate() | ||
const queryClient = useApiQueryClient() | ||
|
@@ -124,23 +120,20 @@ export function CreateSiloSideModalForm() { | |
name="quotas.cpus" | ||
required | ||
units="vCPUs" | ||
validate={validateQuota} | ||
/> | ||
<NumberField | ||
control={form.control} | ||
label="Memory quota" | ||
name="quotas.memory" | ||
required | ||
units="GiB" | ||
validate={validateQuota} | ||
/> | ||
<NumberField | ||
control={form.control} | ||
label="Storage quota" | ||
name="quotas.storage" | ||
required | ||
units="GiB" | ||
validate={validateQuota} | ||
/> | ||
<FormDivider /> | ||
<RadioField | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
/* | ||
* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, you can obtain one at https://mozilla.org/MPL/2.0/. | ||
* | ||
* Copyright Oxide Computer Company | ||
*/ | ||
|
||
import { useState } from 'react' | ||
import { useForm } from 'react-hook-form' | ||
|
||
import { | ||
apiQueryClient, | ||
useApiMutation, | ||
usePrefetchedApiQuery, | ||
type SiloQuotasUpdate, | ||
} from '~/api' | ||
import { NumberField } from '~/components/form/fields/NumberField' | ||
import { SideModalForm } from '~/components/form/SideModalForm' | ||
import { useSiloSelector } from '~/hooks/use-params' | ||
import { Button } from '~/ui/lib/Button' | ||
import { Message } from '~/ui/lib/Message' | ||
import { Table } from '~/ui/lib/Table' | ||
import { classed } from '~/util/classed' | ||
import { links } from '~/util/links' | ||
import { bytesToGiB, GiB } from '~/util/units' | ||
|
||
const Unit = classed.span`ml-1 text-tertiary` | ||
|
||
export function SiloQuotasTab() { | ||
const { silo } = useSiloSelector() | ||
const { data: utilization } = usePrefetchedApiQuery('siloUtilizationView', { | ||
path: { silo: silo }, | ||
}) | ||
|
||
const { allocated: quotas, provisioned } = utilization | ||
|
||
const [editing, setEditing] = useState(false) | ||
|
||
return ( | ||
<> | ||
<Table className="max-w-lg"> | ||
<Table.Header> | ||
<Table.HeaderRow> | ||
<Table.HeadCell>Resource</Table.HeadCell> | ||
<Table.HeadCell>Provisioned</Table.HeadCell> | ||
<Table.HeadCell>Quota</Table.HeadCell> | ||
</Table.HeaderRow> | ||
</Table.Header> | ||
<Table.Body> | ||
<Table.Row> | ||
<Table.Cell>CPU</Table.Cell> | ||
<Table.Cell> | ||
{provisioned.cpus} <Unit>vCPUs</Unit> | ||
</Table.Cell> | ||
<Table.Cell> | ||
{quotas.cpus} <Unit>vCPUs</Unit> | ||
</Table.Cell> | ||
</Table.Row> | ||
<Table.Row> | ||
<Table.Cell>Memory</Table.Cell> | ||
<Table.Cell> | ||
{bytesToGiB(provisioned.memory)} <Unit>GiB</Unit> | ||
</Table.Cell> | ||
<Table.Cell> | ||
{bytesToGiB(quotas.memory)} <Unit>GiB</Unit> | ||
</Table.Cell> | ||
</Table.Row> | ||
<Table.Row> | ||
<Table.Cell>Storage</Table.Cell> | ||
<Table.Cell> | ||
{bytesToGiB(provisioned.storage)} <Unit>GiB</Unit> | ||
</Table.Cell> | ||
<Table.Cell> | ||
{bytesToGiB(quotas.storage)} <Unit>GiB</Unit> | ||
</Table.Cell> | ||
</Table.Row> | ||
</Table.Body> | ||
</Table> | ||
<div className="mt-4 flex space-x-2"> | ||
<Button size="sm" onClick={() => setEditing(true)}> | ||
Edit quotas | ||
</Button> | ||
</div> | ||
{editing && <EditQuotasForm onDismiss={() => setEditing(false)} />} | ||
</> | ||
) | ||
} | ||
|
||
function EditQuotasForm({ onDismiss }: { onDismiss: () => void }) { | ||
const { silo } = useSiloSelector() | ||
const { data: utilization } = usePrefetchedApiQuery('siloUtilizationView', { | ||
path: { silo: silo }, | ||
}) | ||
const quotas = utilization.allocated | ||
|
||
// required because we need to rule out undefined because NumberField hates that | ||
const defaultValues: Required<SiloQuotasUpdate> = { | ||
cpus: quotas.cpus, | ||
memory: bytesToGiB(quotas.memory), | ||
storage: bytesToGiB(quotas.storage), | ||
} | ||
|
||
const form = useForm({ defaultValues }) | ||
|
||
const updateQuotas = useApiMutation('siloQuotasUpdate', { | ||
onSuccess() { | ||
apiQueryClient.invalidateQueries('siloUtilizationView') | ||
onDismiss() | ||
}, | ||
}) | ||
|
||
return ( | ||
<SideModalForm | ||
form={form} | ||
formType="edit" | ||
resourceName="Quotas" | ||
title="Edit quotas" | ||
onDismiss={onDismiss} | ||
onSubmit={({ cpus, memory, storage }) => | ||
updateQuotas.mutate({ | ||
body: { | ||
cpus, | ||
memory: memory * GiB, | ||
// TODO: we use GiB on instance create but TiB on utilization. HM | ||
storage: storage * GiB, | ||
}, | ||
path: { silo }, | ||
}) | ||
} | ||
loading={updateQuotas.isPending} | ||
submitError={updateQuotas.error} | ||
> | ||
<Message content={<LearnMore />} variant="info" /> | ||
|
||
<NumberField name="cpus" label="CPU" units="vCPUs" required control={form.control} /> | ||
<NumberField | ||
name="memory" | ||
label="Memory" | ||
units="GiB" | ||
required | ||
control={form.control} | ||
/> | ||
<NumberField | ||
name="storage" | ||
label="Storage" | ||
units="GiB" | ||
required | ||
control={form.control} | ||
/> | ||
</SideModalForm> | ||
) | ||
} | ||
|
||
function LearnMore() { | ||
return ( | ||
<> | ||
If a quota is set below the amount currently in use, users will not be able to | ||
provision resources. Learn more about quotas in the{' '} | ||
<a | ||
href={links.siloQuotasDocs} | ||
// don't need color and hover color because message text is already color-info anyway | ||
className="underline" | ||
target="_blank" | ||
rel="noreferrer" | ||
> | ||
Silos | ||
</a>{' '} | ||
guide. | ||
</> | ||
) | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think
NaN
wasn't failing RHF's check for a required field the way you'd hope