-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: David Edler <[email protected]>
- Loading branch information
Showing
13 changed files
with
808 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}); |
Oops, something went wrong.