Can view from web UI XML definition of domains
This commit is contained in:
		@@ -25,6 +25,7 @@ import { OIDCCbRoute } from "./routes/auth/OIDCCbRoute";
 | 
			
		||||
import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage";
 | 
			
		||||
import { BaseLoginPage } from "./widgets/BaseLoginPage";
 | 
			
		||||
import { ViewNetworkRoute } from "./routes/ViewNetworkRoute";
 | 
			
		||||
import { VMXMLRoute } from "./routes/VMXMLRoute";
 | 
			
		||||
 | 
			
		||||
interface AuthContext {
 | 
			
		||||
  signedIn: boolean;
 | 
			
		||||
@@ -52,6 +53,7 @@ export function App() {
 | 
			
		||||
          <Route path="vm/:uuid" element={<VMRoute />} />
 | 
			
		||||
          <Route path="vm/:uuid/edit" element={<EditVMRoute />} />
 | 
			
		||||
          <Route path="vm/:uuid/vnc" element={<VNCRoute />} />
 | 
			
		||||
          <Route path="vm/:uuid/xml" element={<VMXMLRoute />} />
 | 
			
		||||
 | 
			
		||||
          <Route path="net" element={<NetworksListRoute />} />
 | 
			
		||||
          <Route path="net/new" element={<CreateNetworkRoute />} />
 | 
			
		||||
 
 | 
			
		||||
@@ -106,7 +106,14 @@ export class APIClient {
 | 
			
		||||
      // JSON response
 | 
			
		||||
      if (res.headers.get("content-type") === "application/json")
 | 
			
		||||
        data = await res.json();
 | 
			
		||||
      // Binary file
 | 
			
		||||
      // Text / XML response
 | 
			
		||||
      else if (
 | 
			
		||||
        ["application/xml", "text/plain"].includes(
 | 
			
		||||
          res.headers.get("content-type") ?? ""
 | 
			
		||||
        )
 | 
			
		||||
      )
 | 
			
		||||
        data = await res.text();
 | 
			
		||||
      // Binary file, tracking download progress
 | 
			
		||||
      else if (res.body !== null && args.downProgress) {
 | 
			
		||||
        // Track download progress
 | 
			
		||||
        const contentEncoding = res.headers.get("content-encoding");
 | 
			
		||||
 
 | 
			
		||||
@@ -113,6 +113,10 @@ export class VMInfo implements VMInfoInterface {
 | 
			
		||||
  get VNCURL(): string {
 | 
			
		||||
    return `/vm/${this.uuid}/vnc`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get XMLURL(): string {
 | 
			
		||||
    return `/vm/${this.uuid}/xml`;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class VMApi {
 | 
			
		||||
@@ -154,6 +158,18 @@ export class VMApi {
 | 
			
		||||
    return new VMInfo(data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the source XML configuration of a domain for debugging purposes
 | 
			
		||||
   */
 | 
			
		||||
  static async GetSingleXML(uuid: string): Promise<string> {
 | 
			
		||||
    return (
 | 
			
		||||
      await APIClient.exec({
 | 
			
		||||
        uri: `/vm/${uuid}/src`,
 | 
			
		||||
        method: "GET",
 | 
			
		||||
      })
 | 
			
		||||
    ).data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Update the information about a single VM
 | 
			
		||||
   */
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,10 @@ import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
			
		||||
import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer";
 | 
			
		||||
import { VMDetails } from "../widgets/vms/VMDetails";
 | 
			
		||||
import { VMStatusWidget } from "../widgets/vms/VMStatusWidget";
 | 
			
		||||
import { Button } from "@mui/material";
 | 
			
		||||
import { Button, IconButton } from "@mui/material";
 | 
			
		||||
import Icon from "@mdi/react";
 | 
			
		||||
import { mdiXml } from "@mdi/js";
 | 
			
		||||
import { RouterLink } from "../widgets/RouterLink";
 | 
			
		||||
 | 
			
		||||
export function VMRoute(): React.ReactElement {
 | 
			
		||||
  const { uuid } = useParams();
 | 
			
		||||
@@ -35,9 +38,15 @@ function VMRouteBody(p: { vm: VMInfo }): React.ReactElement {
 | 
			
		||||
    <VirtWebRouteContainer
 | 
			
		||||
      label={`VM ${p.vm.name}`}
 | 
			
		||||
      actions={
 | 
			
		||||
        <span>
 | 
			
		||||
        <span style={{ display: "inline-flex", alignItems: "center" }}>
 | 
			
		||||
          <VMStatusWidget vm={p.vm} onChange={setState} />
 | 
			
		||||
 | 
			
		||||
          <RouterLink to={p.vm.XMLURL}>
 | 
			
		||||
            <IconButton size="small">
 | 
			
		||||
              <Icon path={mdiXml} style={{ width: "1rem" }} />
 | 
			
		||||
            </IconButton>
 | 
			
		||||
          </RouterLink>
 | 
			
		||||
 | 
			
		||||
          {(state === "Shutdown" || state === "Shutoff") && (
 | 
			
		||||
            <Button
 | 
			
		||||
              variant="contained"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										60
									
								
								virtweb_frontend/src/routes/VMXMLRoute.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								virtweb_frontend/src/routes/VMXMLRoute.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { useParams } from "react-router-dom";
 | 
			
		||||
import { Light as SyntaxHighlighter } from "react-syntax-highlighter";
 | 
			
		||||
import xml from "react-syntax-highlighter/dist/esm/languages/hljs/xml";
 | 
			
		||||
import { dracula } from "react-syntax-highlighter/dist/esm/styles/hljs";
 | 
			
		||||
import xmlFormat from "xml-formatter";
 | 
			
		||||
import { VMApi, VMInfo } from "../api/VMApi";
 | 
			
		||||
import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
			
		||||
import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer";
 | 
			
		||||
import { IconButton } from "@mui/material";
 | 
			
		||||
import { RouterLink } from "../widgets/RouterLink";
 | 
			
		||||
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
 | 
			
		||||
 | 
			
		||||
SyntaxHighlighter.registerLanguage("xml", xml);
 | 
			
		||||
 | 
			
		||||
export function VMXMLRoute(): React.ReactElement {
 | 
			
		||||
  const { uuid } = useParams();
 | 
			
		||||
 | 
			
		||||
  const [vm, setVM] = React.useState<VMInfo | undefined>();
 | 
			
		||||
  const [src, setSrc] = React.useState<string | undefined>();
 | 
			
		||||
 | 
			
		||||
  const load = async () => {
 | 
			
		||||
    setVM(await VMApi.GetSingle(uuid!));
 | 
			
		||||
    setSrc(await VMApi.GetSingleXML(uuid!));
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <AsyncWidget
 | 
			
		||||
      loadKey={uuid}
 | 
			
		||||
      load={load}
 | 
			
		||||
      errMsg="Failed to load VM information!"
 | 
			
		||||
      build={() => <XMLRouteInner vm={vm!} src={src!} />}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function XMLRouteInner(p: { vm: VMInfo; src: string }): React.ReactElement {
 | 
			
		||||
  const xml = xmlFormat(p.src);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <VirtWebRouteContainer
 | 
			
		||||
      label={`XML definition of ${p.vm.name}`}
 | 
			
		||||
      actions={
 | 
			
		||||
        <RouterLink to={p.vm.ViewURL}>
 | 
			
		||||
          <IconButton>
 | 
			
		||||
            <ArrowBackIcon />
 | 
			
		||||
          </IconButton>
 | 
			
		||||
        </RouterLink>
 | 
			
		||||
      }
 | 
			
		||||
    >
 | 
			
		||||
      <SyntaxHighlighter
 | 
			
		||||
        language="xml"
 | 
			
		||||
        style={dracula}
 | 
			
		||||
        customStyle={{ fontSize: "120%" }}
 | 
			
		||||
      >
 | 
			
		||||
        {xml}
 | 
			
		||||
      </SyntaxHighlighter>
 | 
			
		||||
    </VirtWebRouteContainer>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@@ -94,7 +94,7 @@ function VNCInner(p: { vm: VMInfo }): React.ReactElement {
 | 
			
		||||
    return <p>Please wait, connecting to the machine...</p>;
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div ref={vncRef}>
 | 
			
		||||
    <div ref={vncRef} style={{ display: "flex" }}>
 | 
			
		||||
      {/* Controls */}
 | 
			
		||||
      <div>
 | 
			
		||||
        <IconButton onClick={goBack}>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,60 +0,0 @@
 | 
			
		||||
import { FormControl, Input, InputLabel } from "@mui/material";
 | 
			
		||||
import { TextInput } from "./TextInput";
 | 
			
		||||
import { IMaskInput } from "react-imask";
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
interface CustomProps {
 | 
			
		||||
  onChange: (event: { target: { name: string; value: string } }) => void;
 | 
			
		||||
  name: string;
 | 
			
		||||
  placeholder: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const TextMaskCustom = React.forwardRef<HTMLInputElement, CustomProps>(
 | 
			
		||||
  function TextMaskCustom(props, ref) {
 | 
			
		||||
    const { onChange, placeholder, ...other } = props;
 | 
			
		||||
    return (
 | 
			
		||||
      <IMaskInput
 | 
			
		||||
        {...other}
 | 
			
		||||
        mask={placeholder}
 | 
			
		||||
        definitions={{
 | 
			
		||||
          "#": /[1-9]/,
 | 
			
		||||
        }}
 | 
			
		||||
        inputRef={ref}
 | 
			
		||||
        onAccept={(value: any) =>
 | 
			
		||||
          onChange({ target: { name: props.name, value } })
 | 
			
		||||
        }
 | 
			
		||||
        overwrite
 | 
			
		||||
      />
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export function TextMaskInput(p: {
 | 
			
		||||
  label: string;
 | 
			
		||||
  editable: boolean;
 | 
			
		||||
  value?: string;
 | 
			
		||||
  onValueChange?: (newVal: string | undefined) => void;
 | 
			
		||||
  mask: string;
 | 
			
		||||
}): React.ReactElement {
 | 
			
		||||
  const id = React.useRef(Math.random());
 | 
			
		||||
 | 
			
		||||
  if (!p.editable) return <TextInput {...p} />;
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <FormControl variant="standard" fullWidth>
 | 
			
		||||
      <InputLabel htmlFor={`mi-${id.current}`}>{p.label}</InputLabel>
 | 
			
		||||
      <Input
 | 
			
		||||
        fullWidth
 | 
			
		||||
        value={p.value ?? ""}
 | 
			
		||||
        onChange={(c) =>
 | 
			
		||||
          p.onValueChange?.(
 | 
			
		||||
            c.target.value.length === 0 ? undefined : c.target.value
 | 
			
		||||
          )
 | 
			
		||||
        }
 | 
			
		||||
        id={`mi-${id.current}`}
 | 
			
		||||
        inputComponent={TextMaskCustom as any}
 | 
			
		||||
        placeholder={p.mask}
 | 
			
		||||
      />
 | 
			
		||||
    </FormControl>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user