Skip to content

Commit

Permalink
fix(storage) adjust store pool detail header
Browse files Browse the repository at this point in the history
  • Loading branch information
edlerd committed May 10, 2023
1 parent 2d84b1d commit a194ced
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 76 deletions.
36 changes: 28 additions & 8 deletions src/api/storage-pools.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,44 @@
import { handleResponse } from "util/helpers";
import { LxdStorage, LxdStorageResources } from "types/storage";
import { LxdStoragePool, LxdStoragePoolResources } from "types/storage";
import { LxdApiResponse } from "types/apiResponse";

export const fetchStoragePool = (
storage: string,
project: string
): Promise<LxdStorage> => {
): Promise<LxdStoragePool> => {
return new Promise((resolve, reject) => {
fetch(`/1.0/storage-pools/${storage}?project=${project}&recursion=1`)
.then(handleResponse)
.then((data: LxdApiResponse<LxdStorage>) => resolve(data.metadata))
.then((data: LxdApiResponse<LxdStoragePool>) => resolve(data.metadata))
.catch(reject);
});
};

export const fetchStoragePools = (project: string): Promise<LxdStorage[]> => {
export const fetchStoragePools = (
project: string
): Promise<LxdStoragePool[]> => {
return new Promise((resolve, reject) => {
fetch(`/1.0/storage-pools?project=${project}&recursion=1`)
.then(handleResponse)
.then((data: LxdApiResponse<LxdStorage[]>) => resolve(data.metadata))
.then((data: LxdApiResponse<LxdStoragePool[]>) => resolve(data.metadata))
.catch(reject);
});
};

export const fetchStoragePoolResources = (
storage: string
): Promise<LxdStorageResources> => {
): Promise<LxdStoragePoolResources> => {
return new Promise((resolve, reject) => {
fetch(`/1.0/storage-pools/${storage}/resources`)
.then(handleResponse)
.then((data: LxdApiResponse<LxdStorageResources>) =>
.then((data: LxdApiResponse<LxdStoragePoolResources>) =>
resolve(data.metadata)
)
.catch(reject);
});
};

export const createStoragePool = (storage: LxdStorage, project: string) => {
export const createStoragePool = (storage: LxdStoragePool, project: string) => {
return new Promise((resolve, reject) => {
fetch(`/1.0/storage-pools?project=${project}`, {
method: "POST",
Expand All @@ -48,6 +50,24 @@ export const createStoragePool = (storage: LxdStorage, project: string) => {
});
};

export const renameStoragePool = (
oldName: string,
newName: string,
project: string
) => {
return new Promise((resolve, reject) => {
fetch(`/1.0/storage-pools/${oldName}?project=${project}`, {
method: "POST",
body: JSON.stringify({
name: newName,
}),
})
.then(handleResponse)
.then(resolve)
.catch(reject);
});
};

export const deleteStoragePool = (name: string, project: string) => {
return new Promise((resolve, reject) => {
fetch(`/1.0/storage-pools/${name}?project=${project}`, {
Expand Down
4 changes: 2 additions & 2 deletions src/components/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,13 @@ const Navigation: FC = () => {
<NavLink
className="p-side-navigation__link"
to={`/ui/${project}/storage`}
title={`Storage (${project})`}
title={`Storage pools (${project})`}
>
<Icon
className="is-light p-side-navigation__icon"
name="pods"
/>{" "}
Storage
Storage pools
</NavLink>
</li>
<li className="p-side-navigation__item--title secondary">
Expand Down
91 changes: 51 additions & 40 deletions src/pages/storage/StorageDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { FC } from "react";
import { useParams } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import BaseLayout from "components/BaseLayout";
import NotificationRow from "components/NotificationRow";
import { queryKeys } from "util/queryKeys";
import { useNotify } from "context/notify";
Expand All @@ -10,6 +9,7 @@ import Loader from "components/Loader";
import { fetchStoragePool } from "api/storage-pools";
import StorageSize from "pages/storage/StorageSize";
import StorageUsedBy from "pages/storage/StorageUsedBy";
import StorageDetailHeader from "pages/storage/StorageDetailHeader";

const StorageDetail: FC = () => {
const notify = useNotify();
Expand All @@ -26,7 +26,7 @@ const StorageDetail: FC = () => {
}

const {
data: storage,
data: storagePool,
error,
isLoading,
} = useQuery({
Expand All @@ -40,48 +40,59 @@ const StorageDetail: FC = () => {

if (isLoading) {
return <Loader text="Loading storage details..." />;
} else if (!storage) {
} else if (!storagePool) {
return <>Loading storage details failed</>;
}

return (
<BaseLayout title={`Storage details for ${name}`}>
<NotificationRow />
<Row>
<table className="storage-detail-table">
<tbody>
<tr>
<th className="u-text--muted">Name</th>
<td>{storage.name}</td>
</tr>
<tr>
<th className="u-text--muted">Status</th>
<td>{storage.status}</td>
</tr>
<tr>
<th className="u-text--muted">Size</th>
<td>
<StorageSize storage={storage} />
</td>
</tr>
<tr>
<th className="u-text--muted">Source</th>
<td>{storage.config?.source ?? "-"}</td>
</tr>
<tr>
<th className="u-text--muted">Description</th>
<td>{storage.description ? storage.description : "-"}</td>
</tr>
<tr>
<th className="u-text--muted">Driver</th>
<td>{storage.driver}</td>
</tr>
</tbody>
</table>
<h2 className="p-heading--5">Used by</h2>
<StorageUsedBy storage={storage} project={project} />
</Row>
</BaseLayout>
<main className="l-main">
<div className="p-panel instance-detail-page">
<StorageDetailHeader
name={name}
storagePool={storagePool}
project={project}
/>
<div className="p-panel__content">
<NotificationRow />
<Row>
<table className="storage-detail-table">
<tbody>
<tr>
<th className="u-text--muted">Name</th>
<td>{storagePool.name}</td>
</tr>
<tr>
<th className="u-text--muted">Status</th>
<td>{storagePool.status}</td>
</tr>
<tr>
<th className="u-text--muted">Size</th>
<td>
<StorageSize storage={storagePool} />
</td>
</tr>
<tr>
<th className="u-text--muted">Source</th>
<td>{storagePool.config?.source ?? "-"}</td>
</tr>
<tr>
<th className="u-text--muted">Description</th>
<td>
{storagePool.description ? storagePool.description : "-"}
</td>
</tr>
<tr>
<th className="u-text--muted">Driver</th>
<td>{storagePool.driver}</td>
</tr>
</tbody>
</table>
<h2 className="p-heading--5">Used by</h2>
<StorageUsedBy storage={storagePool} project={project} />
</Row>
</div>
</div>
</main>
);
};

Expand Down
74 changes: 74 additions & 0 deletions src/pages/storage/StorageDetailHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React, { FC, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import RenameHeader, { RenameHeaderValues } from "components/RenameHeader";
import { useNotify } from "context/notify";
import { useFormik } from "formik";
import * as Yup from "yup";
import { LxdStoragePool } from "types/storage";
import { renameStoragePool } from "api/storage-pools";
import DeleteStorageBtn from "pages/storage/actions/DeleteStorageBtn";
import { testDuplicateName } from "util/storage";

interface Props {
name: string;
storagePool: LxdStoragePool;
project: string;
}

const StorageDetailHeader: FC<Props> = ({ name, storagePool, project }) => {
const navigate = useNavigate();
const notify = useNotify();
const controllerState = useState<AbortController | null>(null);

const RenameSchema = Yup.object().shape({
name: Yup.string()
.test(...testDuplicateName(project, controllerState))
.required("This field is required"),
});

const formik = useFormik<RenameHeaderValues>({
initialValues: {
name,
isRenaming: false,
},
validationSchema: RenameSchema,
onSubmit: (values) => {
if (name === values.name) {
void formik.setFieldValue("isRenaming", false);
formik.setSubmitting(false);
return;
}
renameStoragePool(name, values.name, project)
.then(() => {
navigate(
`/ui/${project}/storage/${values.name}`,
notify.queue(notify.success("Storage pool renamed."))
);
void formik.setFieldValue("isRenaming", false);
})
.catch((e) => {
notify.failure("Renaming failed", e);
})
.finally(() => formik.setSubmitting(false));
},
});

return (
<RenameHeader
name={name}
parentItem={<Link to={`/ui/${project}/storage`}>Storage pools</Link>}
controls={
<DeleteStorageBtn
key="delete"
storage={storagePool}
project={project}
/>
}
isLoaded={true}
formik={formik}
renameDisabledReason="Cannot rename storage pools"
/>
);
};

export default StorageDetailHeader;
18 changes: 4 additions & 14 deletions src/pages/storage/StorageForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ import { useNotify } from "context/notify";
import { useQueryClient } from "@tanstack/react-query";
import { queryKeys } from "util/queryKeys";
import SubmitButton from "components/SubmitButton";
import { checkDuplicateName } from "util/helpers";
import usePanelParams from "util/usePanelParams";
import { LxdStorage } from "types/storage";
import { LxdStoragePool } from "types/storage";
import { createStoragePool } from "api/storage-pools";
import { getSourceHelpForDriver, storageDrivers } from "util/storageOptions";
import ItemName from "components/ItemName";
import { testDuplicateName } from "util/storage";

const StorageForm: FC = () => {
const panelParams = usePanelParams();
Expand All @@ -31,17 +31,7 @@ const StorageForm: FC = () => {

const StorageSchema = Yup.object().shape({
name: Yup.string()
.test(
"deduplicate",
"A storage pool with this name already exists",
(value) =>
checkDuplicateName(
value,
panelParams.project,
controllerState,
"storage-pools"
)
)
.test(...testDuplicateName(panelParams.project, controllerState))
.required("This field is required"),
});

Expand All @@ -55,7 +45,7 @@ const StorageForm: FC = () => {
},
validationSchema: StorageSchema,
onSubmit: ({ name, description, driver, source, size }) => {
const storagePool: LxdStorage = {
const storagePool: LxdStoragePool = {
name,
description,
driver,
Expand Down
4 changes: 2 additions & 2 deletions src/pages/storage/StorageSize.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import React, { FC } from "react";
import { fetchStoragePoolResources } from "api/storage-pools";
import { useQuery } from "@tanstack/react-query";
import { queryKeys } from "util/queryKeys";
import { LxdStorage } from "types/storage";
import { LxdStoragePool } from "types/storage";
import { humanFileSize } from "util/helpers";
import Meter from "components/Meter";

interface Props {
storage: LxdStorage;
storage: LxdStoragePool;
}

const StorageSize: FC<Props> = ({ storage }) => {
Expand Down
4 changes: 2 additions & 2 deletions src/pages/storage/StorageUsedBy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import React, { FC, Fragment, useState } from "react";
import { Link } from "react-router-dom";
import { List, Tabs } from "@canonical/react-components";
import ImageName from "pages/images/ImageName";
import { LxdStorage } from "types/storage";
import { LxdStoragePool } from "types/storage";
import { filterUsedByType, LxdUsedBy } from "util/usedBy";
import InstanceLink from "pages/instances/InstanceLink";

interface Props {
storage: LxdStorage;
storage: LxdStoragePool;
project: string;
}

Expand Down
Loading

0 comments on commit a194ced

Please sign in to comment.