Skip to content

Commit

Permalink
feat(storage) add volume detail page and adjust rename header
Browse files Browse the repository at this point in the history
  • Loading branch information
edlerd committed Sep 27, 2023
1 parent fb11a65 commit 9c23492
Show file tree
Hide file tree
Showing 30 changed files with 685 additions and 63 deletions.
11 changes: 11 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ const CertificateGenerate = lazy(
const Login = lazy(() => import("pages/login/Login"));
const ProtectedRoute = lazy(() => import("components/ProtectedRoute"));
const StorageDetail = lazy(() => import("pages/storage/StorageDetail"));
const StorageVolumeDetail = lazy(
() => import("pages/storage/StorageVolumeDetail")
);
const NetworkMap = lazy(() => import("pages/networks/NetworkMap"));
const CreateInstanceForm = lazy(
() => import("pages/instances/CreateInstanceForm")
Expand Down Expand Up @@ -251,6 +254,14 @@ const App: FC = () => {
path="/ui/project/:project/storage/detail/:name/:activeTab"
element={<ProtectedRoute outlet={<StorageDetail />} />}
/>
<Route
path="/ui/project/:project/storage/detail/:pool/:type/:volume"
element={<ProtectedRoute outlet={<StorageVolumeDetail />} />}
/>
<Route
path="/ui/project/:project/storage/detail/:pool/:type/:volume/:activeTab"
element={<ProtectedRoute outlet={<StorageVolumeDetail />} />}
/>
<Route
path="/ui/cluster"
element={<ProtectedRoute outlet={<ClusterList />} />}
Expand Down
64 changes: 63 additions & 1 deletion src/api/storage-pools.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { handleResponse } from "util/helpers";
import { handleEtagResponse, handleResponse } from "util/helpers";
import {
LxdStoragePool,
LxdStoragePoolResources,
Expand Down Expand Up @@ -101,6 +101,44 @@ export const fetchStorageVolumes = (
});
};

export const fetchStorageVolume = (
pool: string,
project: string,
type: string,
volume: string
): Promise<LxdStorageVolume> => {
return new Promise((resolve, reject) => {
fetch(
`/1.0/storage-pools/${pool}/volumes/${type}/${volume}?project=${project}&recursion=1`
)
.then(handleEtagResponse)
.then((data) => resolve(data as LxdStorageVolume))
.catch(reject);
});
};

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

export const createIsoStorageVolume = (
pool: string,
isoFile: File,
Expand Down Expand Up @@ -154,6 +192,30 @@ export const createStorageVolume = (
});
};

export const updateStorageVolume = (
pool: string,
project: string,
volume: Partial<LxdStorageVolume>
) => {
return new Promise((resolve, reject) => {
fetch(
`/1.0/storage-pools/${pool}/volumes/${volume.type ?? ""}/${
volume.name ?? ""
}?project=${project}`,
{
method: "PUT",
body: JSON.stringify(volume),
headers: {
"If-Match": volume.etag ?? "invalid-etag",
},
}
)
.then(handleResponse)
.then((data) => resolve(data))
.catch(reject);
});
};

export const deleteStorageVolume = (
volume: string,
pool: string,
Expand Down
10 changes: 7 additions & 3 deletions src/components/RenameHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface RenameHeaderValues {

interface Props {
name: string;
parentItem: ReactNode;
parentItems: ReactNode[];
centerControls?: ReactNode;
controls?: ReactNode;
isLoaded: boolean;
Expand All @@ -20,7 +20,7 @@ interface Props {

const RenameHeader: FC<Props> = ({
name,
parentItem,
parentItems,
centerControls,
controls,
isLoaded,
Expand Down Expand Up @@ -49,7 +49,11 @@ const RenameHeader: FC<Props> = ({
aria-label="Breadcrumbs"
>
<ol className="p-breadcrumbs__items">
<li className="p-breadcrumbs__item">{parentItem}</li>
{parentItems.map((item, key) => (
<li className="p-breadcrumbs__item" key={key}>
{item}
</li>
))}
{formik.values.isRenaming ? (
<li className="p-breadcrumbs__item rename">
<Input
Expand Down
8 changes: 5 additions & 3 deletions src/pages/instances/InstanceDetailHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,11 @@ const InstanceDetailHeader: FC<Props> = ({ name, instance, project }) => {
return (
<RenameHeader
name={name}
parentItem={
<Link to={`/ui/project/${project}/instances`}>Instances</Link>
}
parentItems={[
<Link to={`/ui/project/${project}/instances`} key={1}>
Instances
</Link>,
]}
renameDisabledReason={
instance?.status !== "Stopped"
? "Stop the instance to rename"
Expand Down
6 changes: 5 additions & 1 deletion src/pages/networks/NetworkDetailHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ const NetworkDetailHeader: FC<Props> = ({ name, network, project }) => {
return (
<RenameHeader
name={name}
parentItem={<Link to={`/ui/project/${project}/networks`}>Networks</Link>}
parentItems={[
<Link to={`/ui/project/${project}/networks`} key={1}>
Networks
</Link>,
]}
renameDisabledReason={
!isManaged
? "Can not rename, network is not managed"
Expand Down
6 changes: 5 additions & 1 deletion src/pages/profiles/ProfileDetailHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,11 @@ const ProfileDetailHeader: FC<Props> = ({
return (
<RenameHeader
name={name}
parentItem={<Link to={`/ui/project/${project}/profiles`}>Profiles</Link>}
parentItems={[
<Link to={`/ui/project/${project}/profiles`} key={1}>
Profiles
</Link>,
]}
renameDisabledReason={
profile && profile.name === "default"
? "Cannot rename the default profile"
Expand Down
2 changes: 1 addition & 1 deletion src/pages/projects/ProjectConfigurationHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const ProjectConfigurationHeader: FC<Props> = ({ project }) => {
return (
<RenameHeader
name={project.name}
parentItem="Project configuration"
parentItems={["Project configuration"]}
renameDisabledReason={
project.name === "default"
? "Cannot rename the default project"
Expand Down
7 changes: 5 additions & 2 deletions src/pages/projects/forms/DiskSizeSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import { parseMemoryLimit } from "util/limits";
interface Props {
value?: string;
setMemoryLimit: (val?: string) => void;
disabled?: boolean;
}

const DiskSizeSelector: FC<Props> = ({ value, setMemoryLimit }) => {
const DiskSizeSelector: FC<Props> = ({ value, setMemoryLimit, disabled }) => {
const limit = parseMemoryLimit(value) ?? {
value: 1,
unit: BYTES_UNITS.GIB,
Expand All @@ -32,7 +33,8 @@ const DiskSizeSelector: FC<Props> = ({ value, setMemoryLimit }) => {
step="Any"
placeholder="Enter value"
onChange={(e) => setMemoryLimit(e.target.value + limit.unit)}
value={limit.value}
value={value?.match(/^\d/) ? limit.value : ""}
disabled={disabled}
/>
<Select
id="memUnitSelect"
Expand All @@ -42,6 +44,7 @@ const DiskSizeSelector: FC<Props> = ({ value, setMemoryLimit }) => {
setMemoryLimit(`${limit.value ?? 0}${e.target.value}`)
}
value={limit.unit}
disabled={disabled}
/>
</div>
</div>
Expand Down
5 changes: 5 additions & 0 deletions src/pages/storage/CustomIsoList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
List,
MainTable,
SearchBox,
useNotify,
} from "@canonical/react-components";
import { humanFileSize, isoTimeToString } from "util/helpers";
import { useQuery } from "@tanstack/react-query";
Expand All @@ -20,6 +21,7 @@ interface Props {
}

const CustomIsoList: FC<Props> = ({ project }) => {
const notify = useNotify();
const [query, setQuery] = useState<string>("");

const { data: images = [], isLoading } = useQuery({
Expand Down Expand Up @@ -57,6 +59,9 @@ const CustomIsoList: FC<Props> = ({ project }) => {
pool={image.pool ?? ""}
volume={image.volume}
project={project}
onFinish={() =>
notify.success(`Custom iso ${image.aliases} deleted.`)
}
/>,
]}
/>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/storage/StorageDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const StorageDetail: FC = () => {

return (
<main className="l-main">
<div className="p-panel instance-detail-page">
<div className="p-panel">
<StorageDetailHeader
name={name}
storagePool={storagePool}
Expand Down
12 changes: 7 additions & 5 deletions src/pages/storage/StorageDetailHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ 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/storagePool";
import { testDuplicateStoragePoolName } from "util/storagePool";
import { useNotify } from "@canonical/react-components";

interface Props {
Expand All @@ -22,7 +22,7 @@ const StorageDetailHeader: FC<Props> = ({ name, storagePool, project }) => {

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

Expand Down Expand Up @@ -56,9 +56,11 @@ const StorageDetailHeader: FC<Props> = ({ name, storagePool, project }) => {
return (
<RenameHeader
name={name}
parentItem={
<Link to={`/ui/project/${project}/storage`}>Storage pools</Link>
}
parentItems={[
<Link to={`/ui/project/${project}/storage`} key={1}>
Storage pools
</Link>,
]}
controls={
<DeleteStorageBtn
key="delete"
Expand Down
6 changes: 4 additions & 2 deletions src/pages/storage/StoragePoolForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
zfsDriver,
} from "util/storageOptions";
import ItemName from "components/ItemName";
import { testDuplicateName } from "util/storagePool";
import { testDuplicateStoragePoolName } from "util/storagePool";
import NotificationRow from "components/NotificationRow";

const StoragePoolForm: FC = () => {
Expand All @@ -37,7 +37,9 @@ const StoragePoolForm: FC = () => {

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

Expand Down
14 changes: 7 additions & 7 deletions src/pages/storage/StorageUsedBy.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React, { FC } from "react";
import { Link } from "react-router-dom";
import ImageName from "pages/images/ImageName";
import { LxdStoragePool } from "types/storage";
import { LxdStoragePool, LxdStorageVolume } from "types/storage";
import { filterUsedByType, LxdUsedBy } from "util/usedBy";
import InstanceLink from "pages/instances/InstanceLink";
import ExpandableList from "components/ExpandableList";

interface Props {
storage: LxdStoragePool;
storage: LxdStoragePool | LxdStorageVolume;
project: string;
}

Expand Down Expand Up @@ -36,7 +36,7 @@ const StorageUsedBy: FC<Props> = ({ storage, project }) => {
<td>
<ExpandableList
items={data[INSTANCES].map((item) => (
<div key={item.name}>
<div key={`${item.name}-${item.project}`}>
<InstanceLink instance={item} />
{item.project !== project && ` (project ${item.project})`}
</div>
Expand All @@ -51,7 +51,7 @@ const StorageUsedBy: FC<Props> = ({ storage, project }) => {
<td>
<ExpandableList
items={data[PROFILES].map((item) => (
<div key={item.name}>
<div key={`${item.name}-${item.project}`}>
<Link
to={`/ui/project/${item.project}/profiles/detail/${item.name}`}
>
Expand All @@ -69,7 +69,7 @@ const StorageUsedBy: FC<Props> = ({ storage, project }) => {
<ExpandableList
items={data[IMAGES].map((item) => (
<ImageName
key={item.name}
key={`${item.name}-${item.project}`}
id={item.name}
project={item.project}
/>
Expand All @@ -84,7 +84,7 @@ const StorageUsedBy: FC<Props> = ({ storage, project }) => {
<td>
<ExpandableList
items={data[SNAPSHOTS].map((item) => (
<div key={item.name}>
<div key={`${item.instance}-${item.name}-${item.project}`}>
<Link
to={`/ui/project/${item.project}/instances/detail/${item.instance}/snapshots`}
>
Expand All @@ -101,7 +101,7 @@ const StorageUsedBy: FC<Props> = ({ storage, project }) => {
<td>
<ExpandableList
items={data[CUSTOM].map((item) => (
<div key={item.name}>
<div key={`${item.name}-${item.project}`}>
{item.name}
{item.project !== project && ` (project ${item.project})`}
</div>
Expand Down
Loading

0 comments on commit 9c23492

Please sign in to comment.