Skip to content

Commit

Permalink
feat: [WD-18263] CMS Storage pool usage
Browse files Browse the repository at this point in the history
Signed-off-by: Nkeiruka <[email protected]>
  • Loading branch information
Kxiru committed Feb 27, 2025
1 parent 83f1786 commit 02b3c3c
Show file tree
Hide file tree
Showing 15 changed files with 268 additions and 52 deletions.
35 changes: 34 additions & 1 deletion src/api/storage-pools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,11 @@ export const fetchStoragePools = async (

export const fetchStoragePoolResources = async (
pool: string,
target?: string,
): Promise<LxdStoragePoolResources> => {
const targetParam = target ? `?target=${target}` : "";
return new Promise((resolve, reject) => {
fetch(`/1.0/storage-pools/${pool}/resources`)
fetch(`/1.0/storage-pools/${pool}/resources${targetParam}`)
.then(handleResponse)
.then((data: LxdApiResponse<LxdStoragePoolResources>) => {
resolve(data.metadata);
Expand All @@ -73,6 +75,37 @@ export const fetchStoragePoolResources = async (
});
};

export const fetchClusteredStoragePoolResources = async (
pool: string,
clusterMembers: LxdClusterMember[],
): Promise<LxdStoragePoolResources[]> => {
return new Promise((resolve, reject) => {
Promise.allSettled(
clusterMembers.map(async (member) => {
return fetchStoragePoolResources(pool, member.server_name);
}),
)
.then((results) => {
const poolsOnMembers: LxdStoragePoolResources[] = [];
for (let i = 0; i < clusterMembers.length; i++) {
const memberName = clusterMembers[i].server_name;
const result = results[i];
if (result.status === "rejected") {
reject(constructMemberError(result, memberName));
}
if (result.status === "fulfilled") {
const promise = results[
i
] as PromiseFulfilledResult<LxdStoragePoolResources>;
poolsOnMembers.push({ ...promise.value, memberName: memberName });
}
}
resolve(poolsOnMembers);
})
.catch(reject);
});
};

export const createPool = async (
pool: Partial<LxdStoragePool>,
target?: string,
Expand Down
4 changes: 2 additions & 2 deletions src/components/Meter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const Meter: FC<Props> = ({
hoverText,
}: Props) => {
return (
<>
<div className="p-meter-container">
<div className="p-meter u-no-margin--bottom" title={hoverText}>
<div
style={{ width: `max(${percentage}%, 5px)` }}
Expand All @@ -33,7 +33,7 @@ const Meter: FC<Props> = ({
<div className="p-text--small u-no-margin--bottom u-text--muted">
{text}
</div>
</>
</div>
);
};

Expand Down
5 changes: 4 additions & 1 deletion src/components/ResourceLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { FC } from "react";
import { Link } from "react-router-dom";
import type { ResourceIconType } from "./ResourceIcon";
import ResourceIcon from "./ResourceIcon";
import classNames from "classnames";

interface Props {
type: ResourceIconType;
Expand All @@ -12,7 +13,9 @@ interface Props {
const ResourceLink: FC<Props> = ({ type, value, to }) => {
return (
<Link
className="p-chip is-inline is-dense resource-link"
className={classNames("p-chip is-inline is-dense resource-link", {
"storage-pool-table": true,
})}
to={to}
title={value}
>
Expand Down
14 changes: 14 additions & 0 deletions src/context/useStoragePools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import { useAuth } from "./auth";
import type {
LxdStoragePool,
LXDStoragePoolOnClusterMember,
LxdStoragePoolResources,
} from "types/storage";
import {
fetchClusteredStoragePoolResources,
fetchPoolFromClusterMembers,
fetchStoragePool,
fetchStoragePools,
Expand Down Expand Up @@ -49,3 +51,15 @@ export const usePoolFromClusterMembers = (
enabled: isFineGrained !== null && clusterMembers.length > 0,
});
};

export const useClusteredStoragePoolResources = (
pool: string,
): UseQueryResult<LxdStoragePoolResources[]> => {
const { data: clusterMembers = [] } = useClusterMembers();
return useQuery({
queryKey: [queryKeys.storage, pool, queryKeys.cluster, queryKeys.resources],
queryFn: async () =>
fetchClusteredStoragePoolResources(pool, clusterMembers),
enabled: clusterMembers.length > 0,
});
};
38 changes: 38 additions & 0 deletions src/pages/storage/StoragePoolClusterMember.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import ResourceLink from "components/ResourceLink";
import { useClusterMembers } from "context/useClusterMembers";
import type { FC } from "react";
import type { LxdStoragePool } from "types/storage";
import { hasPoolMemberSpecificSize } from "util/storagePool";

interface Props {
pool: LxdStoragePool;
}

export const StoragePoolClusterMember: FC<Props> = ({ pool }) => {
const hasMemberSpecificSize = hasPoolMemberSpecificSize(pool.driver);
const { data: clusterMembers = [] } = useClusterMembers();

return (
<div>
{hasMemberSpecificSize ? (
clusterMembers.map((member) => {
return (
<div className="clustered-resource-link" key={member.server_name}>
<ResourceLink
to="/ui/cluster"
type={"cluster-member"}
value={member.server_name}
/>
</div>
);
})
) : (
<ResourceLink
to="/ui/cluster"
type={"cluster-group"}
value={"Cluster wide"}
/>
)}
</div>
);
};
12 changes: 11 additions & 1 deletion src/pages/storage/StoragePoolOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import StorageUsedBy from "pages/storage/StorageUsedBy";
import { updateMaxHeight } from "util/updateMaxHeight";
import useEventListener from "util/useEventListener";
import type { LxdStoragePool } from "types/storage";
import { StoragePoolClusterMember } from "./StoragePoolClusterMember";
import { isClusteredServer } from "util/settings";
import { useSettings } from "context/useSettings";
import { hasPoolMemberSpecificSize } from "util/storagePool";

interface Props {
pool: LxdStoragePool;
Expand All @@ -18,6 +22,9 @@ const StoragePoolOverview: FC<Props> = ({ pool, project }) => {
};
useEffect(updateContentHeight, [project, pool]);
useEventListener("resize", updateContentHeight);
const { data: settings } = useSettings();
const isClustered = isClusteredServer(settings);
const hasMemberSpecificSize = hasPoolMemberSpecificSize(pool.driver);

return (
<div className="storage-overview-tab">
Expand All @@ -38,7 +45,10 @@ const StoragePoolOverview: FC<Props> = ({ pool, project }) => {
</tr>
<tr>
<th className="u-text--muted">Size</th>
<td>
<td className="storage-pool-overview-meter">
{hasMemberSpecificSize && isClustered && (
<StoragePoolClusterMember pool={pool} />
)}
<StoragePoolSize pool={pool} hasMeterBar />
</td>
</tr>
Expand Down
15 changes: 15 additions & 0 deletions src/pages/storage/StoragePoolSelectTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import ScrollableTable from "components/ScrollableTable";
import StoragePoolSize from "pages/storage/StoragePoolSize";
import classnames from "classnames";
import { useStoragePools } from "context/useStoragePools";
import { StoragePoolClusterMember } from "./StoragePoolClusterMember";
import { isClusteredServer } from "util/settings";
import { useSettings } from "context/useSettings";

interface Props {
onSelect: (pool: string) => void;
Expand All @@ -15,11 +18,14 @@ interface Props {

const StoragePoolSelectTable: FC<Props> = ({ onSelect, disablePool }) => {
const { data: pools = [], isLoading } = useStoragePools();
const { data: settings } = useSettings();
const isClustered = isClusteredServer(settings);

const headers = [
{ content: "Name", sortKey: "name" },
{ content: "Driver", sortKey: "driver" },
{ content: "Status", sortKey: "status" },
...(isClustered ? [{ content: "Cluster member" }] : []),
{ content: "Size", className: "size" },
{ "aria-label": "Actions", className: "actions" },
];
Expand Down Expand Up @@ -63,6 +69,15 @@ const StoragePoolSelectTable: FC<Props> = ({ onSelect, disablePool }) => {
"aria-label": "Status",
onClick: selectPool,
},
...(isClustered
? [
{
content: <StoragePoolClusterMember pool={pool} />,
role: "rowheader",
"aria-label": "Cluster member",
},
]
: []),
{
content: <StoragePoolSize pool={pool} hasMeterBar />,
role: "cell",
Expand Down
78 changes: 62 additions & 16 deletions src/pages/storage/StoragePoolSize.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,84 @@
import type { FC } from "react";
import { fetchStoragePoolResources } from "api/storage-pools";
import { useQuery } from "@tanstack/react-query";
import { queryKeys } from "util/queryKeys";
import type { LxdStoragePool } from "types/storage";
import { humanFileSize } from "util/helpers";
import Meter from "components/Meter";
import { useClusteredStoragePoolResources } from "context/useStoragePools";
import { isClusteredServer } from "util/settings";
import { useSettings } from "context/useSettings";
import { useQuery } from "@tanstack/react-query";
import { queryKeys } from "util/queryKeys";
import { fetchStoragePoolResources } from "api/storage-pools";
import { hasPoolMemberSpecificSize } from "util/storagePool";

interface Props {
pool: LxdStoragePool;
hasMeterBar?: boolean;
}

const StoragePoolSize: FC<Props> = ({ pool, hasMeterBar }) => {
const { data: resources } = useQuery({
const { data: clusteredPoolResources = [] } =
useClusteredStoragePoolResources(pool.name);
const { data: settings } = useSettings();
const isClustered = isClusteredServer(settings);
const hasMemberSpecificSize =
hasPoolMemberSpecificSize(pool.driver) && isClustered;

const { data: poolResources } = useQuery({
queryKey: [queryKeys.storage, pool.name, queryKeys.resources],
queryFn: async () => fetchStoragePoolResources(pool.name),
enabled: !hasMemberSpecificSize,
});
const resourceList = hasMemberSpecificSize
? clusteredPoolResources
: [poolResources];

if (!resources) {
return <>{pool.config?.size}</>;
if (!hasMeterBar && hasMemberSpecificSize) {
return "Cluster member dependent";
}

const total = resources.space.total;
const used = resources.space.used || 0;
return (
<div>
{resourceList.map((poolResource, index) => {
if (!poolResource) {
return <>{pool.config?.size}</>;
}

if (!hasMeterBar) {
return `${humanFileSize(used)} of ${humanFileSize(total)} used`;
}
const total = poolResource.space.total;
const used = poolResource.space.used || 0;

return (
<Meter
percentage={(100 / total) * used || 0}
text={`${humanFileSize(used)} of ${humanFileSize(total)} used`}
/>
if (!hasMeterBar) {
return (
<div key={index}>
{`${humanFileSize(used)} of ${humanFileSize(total)} used`}
</div>
);
}

return (
<div className="storage-pool-meter-container" key={index}>
{/* {isClustered && (
<ResourceLink
to="/ui/cluster"
type={
hasMemberSpecificSize ? "cluster-member" : "cluster-group"
}
value={
hasMemberSpecificSize && poolResource.memberName
? poolResource.memberName
: "Cluster wide"
}
/>
)} */}

<Meter
key={index}
percentage={(100 / total) * used || 0}
text={`${humanFileSize(used)} of ${humanFileSize(total)} used`}
/>
</div>
);
})}
</div>
);
};

Expand Down
Loading

0 comments on commit 02b3c3c

Please sign in to comment.