Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
Signed-off-by: David Edler <[email protected]>
  • Loading branch information
edlerd authored and GitHub Action committed Feb 26, 2024
1 parent d78b5ff commit 643f385
Show file tree
Hide file tree
Showing 13 changed files with 808 additions and 0 deletions.
63 changes: 63 additions & 0 deletions src/components/NotificationRowLegacy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React, { FC, useEffect, useRef } from "react";
import {
Row,
Notification as NotificationComponent,
NotificationType,
} from "@canonical/react-components";

interface Props {
notification: NotificationType | null;
onDismiss: () => void;
}

const NotificationRowLegacy: FC<Props> = ({
notification = null,
onDismiss,
}) => {
const ref = useRef<HTMLDivElement>(null);

useEffect(() => {
ref.current?.scrollIntoView({
behavior: "smooth",
block: "center",
inline: "start",
});
window.dispatchEvent(new Event("resize"));
}, [notification]);

if (!notification) {
return null;
}
const { actions, title, type, message } = notification;
const figureTitle = () => {
if (title) {
return title;
}
switch (type) {
case "negative":
return "Error";
case "positive":
return "Success";
case "information":
return "Info";
default:
return "";
}
};
return (
<div ref={ref}>
<Row>
<NotificationComponent
title={figureTitle() || undefined}
actions={actions}
severity={type}
onDismiss={onDismiss}
>
{message}
</NotificationComponent>
</Row>
</div>
);
};

export default NotificationRowLegacy;
57 changes: 57 additions & 0 deletions src/components/SubmitButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { FC, MouseEventHandler } from "react";
import { Button, Icon } from "@canonical/react-components";
import classnames from "classnames";

interface Props {
isSubmitting: boolean;
isDisabled: boolean;
buttonLabel: string;
appearance?: string;
processingText?: string;
onClick?: MouseEventHandler<HTMLButtonElement>;
dense?: boolean;
className?: string;
}

const SubmitButton: FC<Props> = ({
isSubmitting,
isDisabled,
buttonLabel,
appearance = "positive",
processingText = "Processing...",
onClick,
dense,
className,
}) => {
return isSubmitting ? (
<Button
appearance={appearance}
type="submit"
hasIcon
disabled
dense={dense}
className={className}
>
<Icon
name="spinner"
className={classnames("u-animation--spin", {
"is-light": appearance === "positive",
})}
/>{" "}
<span>{processingText}</span>
</Button>
) : (
<Button
appearance={appearance}
type="submit"
disabled={isDisabled}
onClick={onClick}
dense={dense}
className={className}
>
{buttonLabel}
</Button>
);
};

export default SubmitButton;
31 changes: 31 additions & 0 deletions src/pages/images/Images.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React, { FC } from "react";
import BaseLayout from "components/BaseLayout";
import NotificationRow from "components/NotificationRow";
import HelpLink from "components/HelpLink";
import ImageList from "pages/images/ImageList";
import { Row } from "@canonical/react-components";
import { useDocs } from "context/useDocs";

const Images: FC = () => {
const docBaseLink = useDocs();

return (
<BaseLayout
title={
<HelpLink
href={`${docBaseLink}/image-handling/`}
title="Learn more about images"
>
Images
</HelpLink>
}
>
<NotificationRow />
<Row>
<ImageList />
</Row>
</BaseLayout>
);
};

export default Images;
96 changes: 96 additions & 0 deletions src/util/config.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { configDescriptionToHtml, toConfigFields } from "./config";
import { LxcConfigOptionCategories } from "types/config";

const exampleConfig: LxcConfigOptionCategories = {
acme: {
keys: [
{
"acme.agree_tos": {
defaultdesc: "`false`",
shortdesc: "Agree to ACME terms of service",
type: "bool",
},
},
{
"acme.ca_url": {
defaultdesc: "`https://acme-v02.api.letsencrypt.org/`",
shortdesc: "",
type: "string",
},
},
],
},
cluster: {
keys: [
{
"cluster.healing_threshold": {
defaultdesc: "`0` (medium)",
shortdesc: "Threshold when to evacuate",
type: "integer",
},
},
{
"cluster.https_address": {
shortdesc: "Address to use for clustering traffic",
type: "string",
},
},
],
},
};

describe("toConfigFields and replaceDocLinks", () => {
it("translates config categories to flat array of fields", () => {
const fields = toConfigFields(exampleConfig);

expect(fields.length).toBe(4);

expect(fields[0].key).toBe("acme.agree_tos");
expect(fields[0].shortdesc).toBe("Agree to ACME terms of service");
expect(fields[0].type).toBe("bool");
expect(fields[0].default).toBe("false");
expect(fields[0].category).toBe("acme");

expect(fields[1].key).toBe("acme.ca_url");
expect(fields[1].shortdesc).toBe("");
expect(fields[1].type).toBe("string");
expect(fields[1].default).toBe("https://acme-v02.api.letsencrypt.org/");
expect(fields[1].category).toBe("acme");

expect(fields[2].key).toBe("cluster.healing_threshold");
expect(fields[2].shortdesc).toBe("Threshold when to evacuate");
expect(fields[2].type).toBe("integer");
expect(fields[2].default).toBe("0");
expect(fields[2].category).toBe("cluster");

expect(fields[3].key).toBe("cluster.https_address");
expect(fields[3].shortdesc).toBe("Address to use for clustering traffic");
expect(fields[3].type).toBe("string");
expect(fields[3].default).toBe("");
expect(fields[3].category).toBe("cluster");
});

it("converts config description to html", () => {
const input =
"Specify a Pongo2 template string that represents the snapshot name.\nThis template is used for scheduled snapshots and for unnamed snapshots.\n\nSee {ref}`instance-options-snapshots-names` for more information.";

const result = configDescriptionToHtml(
input,
"https://docs.example.org",
objectsInvTxt.split("\n"),
);

expect(result).toBe(
'Specify a Pongo2 template string that represents the snapshot name.<br>This template is used for scheduled snapshots and for unnamed snapshots.<br><br>See <a href="https://docs.example.org/reference/instance_options/#instance-options-snapshots-names" target="_blank" rel="noreferrer">instance options snapshots names</a> for more information.',
);
});
});

const objectsInvTxt =
"config:option\n" +
" instance-options-nvidia NVIDIA and CUDA configuration : reference/instance_options/#instance-options-nvidia\n" +
" instance-options-qemu Override QEMU configuration : reference/instance_options/#instance-options-qemu\n" +
" instance-options-raw Raw instance configuration overrides : reference/instance_options/#instance-options-raw\n" +
" instance-options-security Security policies : reference/instance_options/#instance-options-security\n" +
" instance-options-snapshots Snapshot scheduling and configuration : reference/instance_options/#instance-options-snapshots\n" +
" instance-options-snapshots-names Automatic snapshot names : reference/instance_options/#instance-options-snapshots-names";
4 changes: 4 additions & 0 deletions src/util/cookies.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const getCookie = (key: string): string | undefined => {
const val = document.cookie.match(`(^|;)\\s*${key}\\s*=\\s*([^;]+)`);
return val ? val.pop() : undefined;
};
49 changes: 49 additions & 0 deletions src/util/formDevices.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { formDeviceToPayload, parseDevices } from "./formDevices";
import { yamlToObject } from "./yaml";
import { LxdInstance } from "types/instance";
import { dump as dumpYaml } from "js-yaml";

const deviceYaml =
"devices:\n" +
" root:\n" +
" path: /\n" +
" pool: big-pool\n" +
" type: disk\n" +
" eth0:\n" +
" network: lxcbr\n" +
" type: nic\n" +
" eth1:\n" +
" ipv4.address: 10.76.171.21\n" +
" name: eth0\n" +
" network: mybr\n" +
" type: nic\n" +
" grafananat:\n" +
" connect: tcp:10.76.171.21:3000\n" +
" listen: tcp:192.168.0.90:3000\n" +
" nat: 'true'\n" +
" type: proxy\n" +
" prometheusnat:\n" +
" connect: tcp:10.76.171.21:9090\n" +
" listen: tcp:192.168.0.90:9090\n" +
" nat: 'true'\n" +
" type: proxy\n" +
"";

describe("parseDevices and formDeviceToPayload", () => {
it("preserves disk, network and custom devices", () => {
const instance = yamlToObject(deviceYaml) as LxdInstance;
const formDevices = parseDevices(instance.devices);
const payload = formDeviceToPayload(formDevices);

const matchFormDeviceType = (deviceType: string) =>
Object.values(formDevices).filter((item) => item.type === deviceType);

expect(matchFormDeviceType("disk").length).toBe(1);
expect(matchFormDeviceType("nic").length).toBe(1);
expect(matchFormDeviceType("custom-nic").length).toBe(1);
expect(matchFormDeviceType("unknown").length).toBe(2);

const outYaml = dumpYaml({ devices: payload });
expect(outYaml).toBe(deviceYaml);
});
});
Loading

0 comments on commit 643f385

Please sign in to comment.