Skip to content

Commit

Permalink
feat(storage) add pool configuration as yaml WD-8614
Browse files Browse the repository at this point in the history
Signed-off-by: David Edler <david.edler@canonical.com>
  • Loading branch information
edlerd committed Mar 11, 2024
1 parent fedca78 commit 58023d4
Show file tree
Hide file tree
Showing 18 changed files with 357 additions and 126 deletions.
27 changes: 27 additions & 0 deletions src/components/forms/YamlConfirmation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { FC } from "react";
import { ConfirmationModal } from "@canonical/react-components";

interface Props {
onConfirm: () => void;
close: () => void;
}

const YamlConfirmation: FC<Props> = ({ onConfirm, close }) => {
return (
<ConfirmationModal
confirmButtonLabel="Discard changes"
onConfirm={onConfirm}
close={close}
title="Confirm"
>
<p>
Switching back to guided forms will discard all changes in the YAML
editor.
<br />
Are you sure you want to proceed?
</p>
</ConfirmationModal>
);
};

export default YamlConfirmation;
6 changes: 5 additions & 1 deletion src/components/forms/YamlForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { updateMaxHeight } from "util/updateMaxHeight";
import useEventListener from "@use-it/event-listener";
import { editor } from "monaco-editor/esm/vs/editor/editor.api";
import IStandaloneCodeEditor = editor.IStandaloneCodeEditor;
import classnames from "classnames";

export interface YamlFormValues {
yaml?: string;
Expand Down Expand Up @@ -46,7 +47,10 @@ const YamlForm: FC<Props> = ({
return (
<>
{children}
<div ref={containerRef} className="code-editor-wrapper">
<div
ref={containerRef}
className={classnames("code-editor-wrapper", { "read-only": readOnly })}
>
<Editor
defaultValue={yaml}
language="yaml"
Expand Down
23 changes: 15 additions & 8 deletions src/pages/instances/CreateInstance.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FC, ReactNode, useEffect, useState } from "react";
import React, { FC, ReactNode, useEffect, useState } from "react";
import {
ActionButton,
Button,
Expand Down Expand Up @@ -74,6 +74,7 @@ import {
} from "util/instanceValidation";
import FormFooterLayout from "components/forms/FormFooterLayout";
import { useToastNotification } from "context/toastNotificationProvider";
import { useDocs } from "context/useDocs";

export type CreateInstanceFormValues = InstanceDetailsFormValues &
FormDeviceValues &
Expand All @@ -91,6 +92,7 @@ interface PresetFormState {
}

const CreateInstance: FC = () => {
const docBaseLink = useDocs();
const eventQueue = useEventQueue();
const location = useLocation() as Location<PresetFormState | null>;
const navigate = useNavigate();
Expand Down Expand Up @@ -381,6 +383,7 @@ const CreateInstance: FC = () => {
<Form onSubmit={formik.handleSubmit} className="form">
<InstanceFormMenu
active={section}
formik={formik}
setActive={updateSection}
isConfigDisabled={!formik.values.image}
isConfigOpen={!!formik.values.image && isConfigOpen}
Expand Down Expand Up @@ -421,16 +424,20 @@ const CreateInstance: FC = () => {

{section === YAML_CONFIGURATION && (
<YamlForm
key={`yaml-form-${formik.values.readOnly}`}
yaml={getYaml()}
setYaml={(yaml) => void formik.setFieldValue("yaml", yaml)}
>
<Notification
severity="caution"
title="Before you edit the YAML"
titleElement="h2"
>
Changes will be discarded, when switching back to the guided
forms.
<Notification severity="information" title="YAML Configuration">
This is the YAML representation of the instance.
<br />
<a
href={`${docBaseLink}/instances`}
target="_blank"
rel="noopener noreferrer"
>
Learn more about instances
</a>
</Notification>
</YamlForm>
)}
Expand Down
24 changes: 15 additions & 9 deletions src/pages/instances/EditInstance.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FC, useEffect, useState } from "react";
import React, { FC, useEffect, useState } from "react";
import {
ActionButton,
Button,
Expand Down Expand Up @@ -55,6 +55,7 @@ import { hasDiskError, hasNetworkError } from "util/instanceValidation";
import FormFooterLayout from "components/forms/FormFooterLayout";
import { useToastNotification } from "context/toastNotificationProvider";
import InstanceLink from "pages/instances/InstanceLink";
import { useDocs } from "context/useDocs";

export interface InstanceEditDetailsFormValues {
name: string;
Expand All @@ -79,6 +80,7 @@ interface Props {
}

const EditInstance: FC<Props> = ({ instance }) => {
const docBaseLink = useDocs();
const eventQueue = useEventQueue();
const toastNotify = useToastNotification();
const { project, section } = useParams<{
Expand Down Expand Up @@ -185,6 +187,7 @@ const EditInstance: FC<Props> = ({ instance }) => {
toggleConfigOpen={toggleMenu}
hasDiskError={hasDiskError(formik)}
hasNetworkError={hasNetworkError(formik)}
formik={formik}
/>
<Row className="form-contents" key={section}>
<Col size={12}>
Expand Down Expand Up @@ -218,19 +221,22 @@ const EditInstance: FC<Props> = ({ instance }) => {

{section === slugify(YAML_CONFIGURATION) && (
<YamlForm
key={`yaml-form-${formik.values.readOnly}`}
yaml={getYaml()}
setYaml={(yaml) => void formik.setFieldValue("yaml", yaml)}
readOnly={readOnly}
>
{!readOnly && (
<Notification
severity="caution"
title="Before you edit the YAML"
<Notification severity="information" title="YAML Configuration">
This is the YAML representation of the instance.
<br />
<a
href={`${docBaseLink}/instances`}
target="_blank"
rel="noopener noreferrer"
>
Changes will be discarded, when switching back to the guided
forms.
</Notification>
)}
Learn more about instances
</a>
</Notification>
</YamlForm>
)}
</Col>
Expand Down
27 changes: 25 additions & 2 deletions src/pages/instances/forms/InstanceFormMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { FC, useEffect } from "react";
import React, { FC, ReactNode, useEffect, useState } from "react";
import MenuItem from "components/forms/FormMenuItem";
import { Button, useNotify } from "@canonical/react-components";
import { updateMaxHeight } from "util/updateMaxHeight";
import useEventListener from "@use-it/event-listener";
import YamlConfirmation from "components/forms/YamlConfirmation";
import { InstanceAndProfileFormikProps } from "components/forms/instanceAndProfileFormValues";

export const MAIN_CONFIGURATION = "Main configuration";
export const DISK_DEVICES = "Disk devices";
Expand All @@ -21,6 +23,7 @@ interface Props {
setActive: (val: string) => void;
hasDiskError: boolean;
hasNetworkError: boolean;
formik: InstanceAndProfileFormikProps;
}

const InstanceFormMenu: FC<Props> = ({
Expand All @@ -31,11 +34,30 @@ const InstanceFormMenu: FC<Props> = ({
setActive,
hasDiskError,
hasNetworkError,
formik,
}) => {
const notify = useNotify();
const [confirmModal, setConfirmModal] = useState<ReactNode | null>(null);
const menuItemProps = {
active,
setActive,
setActive: (val: string) => {
if (Boolean(formik.values.yaml) && val !== YAML_CONFIGURATION) {
const handleConfirm = () => {
void formik.setFieldValue("yaml", undefined);
setConfirmModal(null);
setActive(val);
};

setConfirmModal(
<YamlConfirmation
onConfirm={handleConfirm}
close={() => setConfirmModal(null)}
/>,
);
} else {
setActive(val);
}
},
};

const resize = () => {
Expand All @@ -46,6 +68,7 @@ const InstanceFormMenu: FC<Props> = ({

return (
<div className="p-side-navigation--accordion form-navigation">
{confirmModal}
<nav aria-label="Instance form navigation">
<ul className="p-side-navigation__list">
<MenuItem label={MAIN_CONFIGURATION} {...menuItemProps} />
Expand Down
12 changes: 5 additions & 7 deletions src/pages/networks/CreateNetwork.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import { fetchClusterMembers } from "api/cluster";
import BaseLayout from "components/BaseLayout";
import { MAIN_CONFIGURATION } from "pages/networks/forms/NetworkFormMenu";
import { slugify } from "util/slugify";
import { YAML_CONFIGURATION } from "pages/profiles/forms/ProfileFormMenu";
import FormFooterLayout from "components/forms/FormFooterLayout";
import { useToastNotification } from "context/toastNotificationProvider";

Expand Down Expand Up @@ -105,6 +104,10 @@ const CreateNetwork: FC = () => {
return dumpYaml(payload);
};

const updateSection = (newSection: string) => {
setSection(slugify(newSection));
};

return (
<BaseLayout title="Create a network" contentClassName="create-network">
<NotificationRow />
Expand All @@ -113,12 +116,7 @@ const CreateNetwork: FC = () => {
getYaml={getYaml}
project={project}
section={section}
setSection={(section) => {
if (Boolean(formik.values.yaml) && section !== YAML_CONFIGURATION) {
void formik.setFieldValue("yaml", undefined);
}
setSection(slugify(section));
}}
setSection={updateSection}
/>
<FormFooterLayout>
<Button
Expand Down
50 changes: 5 additions & 45 deletions src/pages/networks/EditNetwork.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@ import NetworkForm, {
import { LxdNetwork } from "types/network";
import { yamlToObject } from "util/yaml";
import { dump as dumpYaml } from "js-yaml";
import { getNetworkEditValues, handleConfigKeys } from "util/networkEdit";
import { toNetworkFormValues } from "util/networkForm";
import { slugify } from "util/slugify";
import { useNavigate, useParams } from "react-router-dom";
import { MAIN_CONFIGURATION } from "pages/networks/forms/NetworkFormMenu";
import { YAML_CONFIGURATION } from "pages/profiles/forms/ProfileFormMenu";
import FormFooterLayout from "components/forms/FormFooterLayout";
import { useToastNotification } from "context/toastNotificationProvider";

Expand Down Expand Up @@ -55,14 +54,14 @@ const EditNetwork: FC<Props> = ({ network, project }) => {
});

const formik = useFormik<NetworkFormValues>({
initialValues: getNetworkEditValues(network),
initialValues: toNetworkFormValues(network),
validationSchema: NetworkSchema,
onSubmit: (values) => {
const yaml = values.yaml ? values.yaml : getYaml();
const saveNetwork = yamlToObject(yaml) as LxdNetwork;
updateNetwork({ ...saveNetwork, etag: network.etag }, project)
.then(() => {
void formik.setValues(getNetworkEditValues(saveNetwork));
void formik.setValues(toNetworkFormValues(saveNetwork));
void queryClient.invalidateQueries({
queryKey: [
queryKeys.projects,
Expand All @@ -80,50 +79,11 @@ const EditNetwork: FC<Props> = ({ network, project }) => {
},
});

const getPayload = (values: NetworkFormValues) => {
const formNetwork = toNetwork(values);

const excludeMainKeys = new Set([
"used_by",
"etag",
"status",
"locations",
"managed",
"name",
"description",
"config",
"type",
]);
const missingMainFields = Object.fromEntries(
Object.entries(network).filter((e) => !excludeMainKeys.has(e[0])),
);

const excludeConfigKeys = new Set(handleConfigKeys);
const missingConfigFields = Object.fromEntries(
Object.entries(network.config).filter(
(e) => !excludeConfigKeys.has(e[0]) && !e[0].startsWith("volatile"),
),
);

return {
...missingMainFields,
...formNetwork,
config: {
...formNetwork.config,
...missingConfigFields,
},
};
};

const getYaml = () => {
return dumpYaml(getPayload(formik.values));
return dumpYaml(toNetwork(formik.values));
};

const setSection = (newSection: string) => {
if (Boolean(formik.values.yaml) && section !== YAML_CONFIGURATION) {
void formik.setFieldValue("yaml", undefined);
}

const baseUrl = `/ui/project/${project}/network/${network.name}/configuration`;
newSection === MAIN_CONFIGURATION
? navigate(baseUrl)
Expand Down Expand Up @@ -153,7 +113,7 @@ const EditNetwork: FC<Props> = ({ network, project }) => {
<>
<Button
appearance="base"
onClick={() => formik.setValues(getNetworkEditValues(network))}
onClick={() => formik.setValues(toNetworkFormValues(network))}
>
Cancel
</Button>
Expand Down
Loading

0 comments on commit 58023d4

Please sign in to comment.