Create frames for network filters management
This commit is contained in:
		@@ -27,6 +27,11 @@ import { BaseLoginPage } from "./widgets/BaseLoginPage";
 | 
			
		||||
import { ViewNetworkRoute } from "./routes/ViewNetworkRoute";
 | 
			
		||||
import { HomeRoute } from "./routes/HomeRoute";
 | 
			
		||||
import { NetworkFiltersListRoute } from "./routes/NetworkFiltersListRoute";
 | 
			
		||||
import { ViewNWFilterRoute } from "./routes/ViewNWFilterRoute";
 | 
			
		||||
import {
 | 
			
		||||
  CreateNWFilterRoute,
 | 
			
		||||
  EditNWFilterRoute,
 | 
			
		||||
} from "./routes/EditNWFilterRoute";
 | 
			
		||||
 | 
			
		||||
interface AuthContext {
 | 
			
		||||
  signedIn: boolean;
 | 
			
		||||
@@ -63,6 +68,9 @@ export function App() {
 | 
			
		||||
          <Route path="net/:uuid/edit" element={<EditNetworkRoute />} />
 | 
			
		||||
 | 
			
		||||
          <Route path="nwfilter" element={<NetworkFiltersListRoute />} />
 | 
			
		||||
          <Route path="nwfilter/new" element={<CreateNWFilterRoute />} />
 | 
			
		||||
          <Route path="nwfilter/:uuid" element={<ViewNWFilterRoute />} />
 | 
			
		||||
          <Route path="nwfilter/:uuid/edit" element={<EditNWFilterRoute />} />
 | 
			
		||||
 | 
			
		||||
          <Route path="sysinfo" element={<SysInfoRoute />} />
 | 
			
		||||
          <Route path="*" element={<NotFoundRoute />} />
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
import { APIClient } from "./ApiClient";
 | 
			
		||||
import { ServerApi } from "./ServerApi";
 | 
			
		||||
 | 
			
		||||
export interface NWFilterChain {
 | 
			
		||||
  protocol: string;
 | 
			
		||||
@@ -18,8 +19,91 @@ export interface NWFSMac {
 | 
			
		||||
  comment?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO : complete
 | 
			
		||||
export type NWFSelector = NWFSAll | NWFSMac;
 | 
			
		||||
export interface NWFSArpOrRARP {
 | 
			
		||||
  srcmacaddr?: string;
 | 
			
		||||
  srcmacmask?: string;
 | 
			
		||||
  dstmacaddr?: string;
 | 
			
		||||
  dstmacmask?: string;
 | 
			
		||||
  arpsrcipaddr?: string;
 | 
			
		||||
  arpsrcipmask?: number;
 | 
			
		||||
  arpdstipaddr?: string;
 | 
			
		||||
  arpdstipmask?: number;
 | 
			
		||||
  comment?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type NWFSArp = NWFSArpOrRARP & {
 | 
			
		||||
  type: "arp";
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type NWFSRArp = NWFSArpOrRARP & {
 | 
			
		||||
  type: "rarp";
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export interface NWFSIPBase {
 | 
			
		||||
  srcmacaddr?: string;
 | 
			
		||||
  srcmacmask?: string;
 | 
			
		||||
  dstmacaddr?: string;
 | 
			
		||||
  dstmacmask?: string;
 | 
			
		||||
  srcipaddr?: string;
 | 
			
		||||
  srcipmask?: number;
 | 
			
		||||
  dstipaddr?: string;
 | 
			
		||||
  dstipmask?: number;
 | 
			
		||||
  comment?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type NFWSIPv4 = NWFSIPBase & { type: "ipv4" };
 | 
			
		||||
export type NFWSIPv6 = NWFSIPBase & { type: "ipv6" };
 | 
			
		||||
 | 
			
		||||
export type Layer4State =
 | 
			
		||||
  | "NEW"
 | 
			
		||||
  | "ESTABLISHED"
 | 
			
		||||
  | "RELATED"
 | 
			
		||||
  | "INVALID"
 | 
			
		||||
  | "NONE";
 | 
			
		||||
 | 
			
		||||
export interface NWFSLayer4Base {
 | 
			
		||||
  srcmacaddr?: string;
 | 
			
		||||
  srcipaddr?: string;
 | 
			
		||||
  srcipmask?: number;
 | 
			
		||||
  dstipaddr?: string;
 | 
			
		||||
  dstipmask?: number;
 | 
			
		||||
  srcipfrom?: string;
 | 
			
		||||
  srcipto?: string;
 | 
			
		||||
  dstipfrom?: string;
 | 
			
		||||
  dstipto?: string;
 | 
			
		||||
  srcportstart?: number;
 | 
			
		||||
  srcportend?: number;
 | 
			
		||||
  dstportstart?: number;
 | 
			
		||||
  dstportend?: number;
 | 
			
		||||
  state?: Layer4State;
 | 
			
		||||
  comment?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type NFWSTCPv4 = NWFSLayer4Base & { type: "tcp" };
 | 
			
		||||
export type NFWSUDPv4 = NWFSLayer4Base & { type: "udp" };
 | 
			
		||||
export type NFWSSCTPv4 = NWFSLayer4Base & { type: "sctp" };
 | 
			
		||||
export type NFWSICMPv4 = NWFSLayer4Base & { type: "icmp" };
 | 
			
		||||
 | 
			
		||||
export type NFWSTCPv6 = NWFSLayer4Base & { type: "tcpipv6" };
 | 
			
		||||
export type NFWSUDPv6 = NWFSLayer4Base & { type: "udpipv6" };
 | 
			
		||||
export type NFWSSCTPv6 = NWFSLayer4Base & { type: "sctpipv6" };
 | 
			
		||||
export type NFWSICMPv6 = NWFSLayer4Base & { type: "icmpipv6" };
 | 
			
		||||
 | 
			
		||||
export type NWFSelector =
 | 
			
		||||
  | NWFSAll
 | 
			
		||||
  | NWFSMac
 | 
			
		||||
  | NWFSArp
 | 
			
		||||
  | NWFSRArp
 | 
			
		||||
  | NFWSIPv4
 | 
			
		||||
  | NFWSIPv6
 | 
			
		||||
  | NFWSTCPv4
 | 
			
		||||
  | NFWSUDPv4
 | 
			
		||||
  | NFWSSCTPv4
 | 
			
		||||
  | NFWSICMPv4
 | 
			
		||||
  | NFWSTCPv6
 | 
			
		||||
  | NFWSUDPv6
 | 
			
		||||
  | NFWSSCTPv6
 | 
			
		||||
  | NFWSICMPv6;
 | 
			
		||||
 | 
			
		||||
export interface NWFilterRule {
 | 
			
		||||
  action: "drop" | "reject" | "accept" | "return" | "continue";
 | 
			
		||||
@@ -41,6 +125,10 @@ export function NWFilterURL(n: NWFilter, edit: boolean = false): string {
 | 
			
		||||
  return `/nwfilter/${n.uuid}${edit ? "/edit" : ""}`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function NWFilterIsBuiltin(n: NWFilter): boolean {
 | 
			
		||||
  return ServerApi.Config.builtin_nwfilter_rules.includes(n.name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class NWFilterApi {
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the entire list of networks
 | 
			
		||||
@@ -57,4 +145,64 @@ export class NWFilterApi {
 | 
			
		||||
 | 
			
		||||
    return list;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the information about a single network filter
 | 
			
		||||
   */
 | 
			
		||||
  static async GetSingle(uuid: string): Promise<NWFilter> {
 | 
			
		||||
    return (
 | 
			
		||||
      await APIClient.exec({
 | 
			
		||||
        method: "GET",
 | 
			
		||||
        uri: `/nwfilter/${uuid}`,
 | 
			
		||||
      })
 | 
			
		||||
    ).data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the source XML configuration of a network filter for debugging purposes
 | 
			
		||||
   */
 | 
			
		||||
  static async GetSingleXML(uuid: string): Promise<string> {
 | 
			
		||||
    return (
 | 
			
		||||
      await APIClient.exec({
 | 
			
		||||
        uri: `/nwfilter/${uuid}/src`,
 | 
			
		||||
        method: "GET",
 | 
			
		||||
      })
 | 
			
		||||
    ).data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Create a new network filter
 | 
			
		||||
   */
 | 
			
		||||
  static async Create(n: NWFilter): Promise<{ uid: string }> {
 | 
			
		||||
    return (
 | 
			
		||||
      await APIClient.exec({
 | 
			
		||||
        method: "POST",
 | 
			
		||||
        uri: "/nwfilter/create",
 | 
			
		||||
        jsonData: n,
 | 
			
		||||
      })
 | 
			
		||||
    ).data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Update an existing network filter
 | 
			
		||||
   */
 | 
			
		||||
  static async Update(n: NWFilter): Promise<{ uid: string }> {
 | 
			
		||||
    return (
 | 
			
		||||
      await APIClient.exec({
 | 
			
		||||
        method: "PUT",
 | 
			
		||||
        uri: `/nwfilter/${n.uuid}`,
 | 
			
		||||
        jsonData: n,
 | 
			
		||||
      })
 | 
			
		||||
    ).data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Delete a network filter
 | 
			
		||||
   */
 | 
			
		||||
  static async Delete(n: NWFilter): Promise<void> {
 | 
			
		||||
    await APIClient.exec({
 | 
			
		||||
      method: "DELETE",
 | 
			
		||||
      uri: `/nwfilter/${n.uuid}`,
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -160,12 +160,10 @@ export class NetworkApi {
 | 
			
		||||
  /**
 | 
			
		||||
   * Delete a network
 | 
			
		||||
   */
 | 
			
		||||
  static async Delete(n: NetworkInfo): Promise<NetworkInfo[]> {
 | 
			
		||||
    return (
 | 
			
		||||
      await APIClient.exec({
 | 
			
		||||
        method: "DELETE",
 | 
			
		||||
        uri: `/network/${n.uuid}`,
 | 
			
		||||
      })
 | 
			
		||||
    ).data;
 | 
			
		||||
  static async Delete(n: NetworkInfo): Promise<void> {
 | 
			
		||||
    await APIClient.exec({
 | 
			
		||||
      method: "DELETE",
 | 
			
		||||
      uri: `/network/${n.uuid}`,
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										150
									
								
								virtweb_frontend/src/routes/EditNWFilterRoute.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								virtweb_frontend/src/routes/EditNWFilterRoute.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,150 @@
 | 
			
		||||
import { Button } from "@mui/material";
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { useNavigate, useParams } from "react-router-dom";
 | 
			
		||||
import { useAlert } from "../hooks/providers/AlertDialogProvider";
 | 
			
		||||
import { useLoadingMessage } from "../hooks/providers/LoadingMessageProvider";
 | 
			
		||||
import { useSnackbar } from "../hooks/providers/SnackbarProvider";
 | 
			
		||||
import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
			
		||||
import { ConfigImportExportButtons } from "../widgets/ConfigImportExportButtons";
 | 
			
		||||
import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer";
 | 
			
		||||
import { NWFilter, NWFilterApi, NWFilterURL } from "../api/NWFilterApi";
 | 
			
		||||
import { NWFilterDetails } from "../widgets/nwfilter/NWFilterDetails";
 | 
			
		||||
 | 
			
		||||
export function CreateNWFilterRoute(): React.ReactElement {
 | 
			
		||||
  const alert = useAlert();
 | 
			
		||||
  const snackbar = useSnackbar();
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
 | 
			
		||||
  const [nwfilter, setNWFilter] = React.useState<NWFilter>({
 | 
			
		||||
    name: "my-filter",
 | 
			
		||||
    join_filters: [],
 | 
			
		||||
    rules: [],
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const createNWFilter = async (n: NWFilter) => {
 | 
			
		||||
    try {
 | 
			
		||||
      const res = await NWFilterApi.Create(n);
 | 
			
		||||
      snackbar("The network filter was successfully created!");
 | 
			
		||||
      navigate(`/nwfilter/${res.uid}`);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.error(e);
 | 
			
		||||
      alert(`Failed to create network filter!\n${e}`);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <EditNetworkFilterRouteInner
 | 
			
		||||
      nwfilter={nwfilter}
 | 
			
		||||
      creating={true}
 | 
			
		||||
      onCancel={() => navigate("/nwfilter")}
 | 
			
		||||
      onSave={createNWFilter}
 | 
			
		||||
      onReplace={setNWFilter}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function EditNWFilterRoute(): React.ReactElement {
 | 
			
		||||
  const alert = useAlert();
 | 
			
		||||
  const snackbar = useSnackbar();
 | 
			
		||||
 | 
			
		||||
  const { uuid } = useParams();
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
 | 
			
		||||
  const [nwfilter, setNWFilter] = React.useState<NWFilter | undefined>();
 | 
			
		||||
 | 
			
		||||
  const load = async () => {
 | 
			
		||||
    setNWFilter(await NWFilterApi.GetSingle(uuid!));
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const updateNetworkFilter = async (n: NWFilter) => {
 | 
			
		||||
    try {
 | 
			
		||||
      await NWFilterApi.Update(n);
 | 
			
		||||
      snackbar("The network filter was successfully updated!");
 | 
			
		||||
      navigate(NWFilterURL(nwfilter!));
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.error(e);
 | 
			
		||||
      alert(`Failed to update network filter!\n${e}`);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <AsyncWidget
 | 
			
		||||
      loadKey={uuid}
 | 
			
		||||
      ready={nwfilter !== undefined}
 | 
			
		||||
      errMsg="Failed to fetch network filter information!"
 | 
			
		||||
      load={load}
 | 
			
		||||
      build={() => (
 | 
			
		||||
        <EditNetworkFilterRouteInner
 | 
			
		||||
          nwfilter={nwfilter!}
 | 
			
		||||
          creating={false}
 | 
			
		||||
          onCancel={() => navigate(`/nwfilter/${uuid}`)}
 | 
			
		||||
          onSave={updateNetworkFilter}
 | 
			
		||||
          onReplace={setNWFilter}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function EditNetworkFilterRouteInner(p: {
 | 
			
		||||
  nwfilter: NWFilter;
 | 
			
		||||
  creating: boolean;
 | 
			
		||||
  onCancel: () => void;
 | 
			
		||||
  onSave: (vm: NWFilter) => Promise<void>;
 | 
			
		||||
  onReplace: (vm: NWFilter) => void;
 | 
			
		||||
}): React.ReactElement {
 | 
			
		||||
  const loadingMessage = useLoadingMessage();
 | 
			
		||||
 | 
			
		||||
  const [changed, setChanged] = React.useState(false);
 | 
			
		||||
 | 
			
		||||
  const [, updateState] = React.useState<any>();
 | 
			
		||||
  const forceUpdate = React.useCallback(() => updateState({}), []);
 | 
			
		||||
 | 
			
		||||
  const valueChanged = () => {
 | 
			
		||||
    setChanged(true);
 | 
			
		||||
    forceUpdate();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const save = async () => {
 | 
			
		||||
    loadingMessage.show("Saving network filter configuration...");
 | 
			
		||||
    await p.onSave(p.nwfilter);
 | 
			
		||||
    loadingMessage.hide();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <VirtWebRouteContainer
 | 
			
		||||
      label={p.creating ? "Create a Network Filter" : "Edit Network Filter"}
 | 
			
		||||
      actions={
 | 
			
		||||
        <span>
 | 
			
		||||
          <ConfigImportExportButtons
 | 
			
		||||
            currentConf={p.nwfilter}
 | 
			
		||||
            filename={`nwfilter-${p.nwfilter.name}.json`}
 | 
			
		||||
            importConf={(c) => {
 | 
			
		||||
              p.onReplace(c);
 | 
			
		||||
              valueChanged();
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
 | 
			
		||||
          {changed && (
 | 
			
		||||
            <Button
 | 
			
		||||
              variant="contained"
 | 
			
		||||
              onClick={save}
 | 
			
		||||
              style={{ marginRight: "10px" }}
 | 
			
		||||
            >
 | 
			
		||||
              {p.creating ? "Create" : "Save"}
 | 
			
		||||
            </Button>
 | 
			
		||||
          )}
 | 
			
		||||
          <Button onClick={p.onCancel} variant="outlined">
 | 
			
		||||
            Cancel
 | 
			
		||||
          </Button>
 | 
			
		||||
        </span>
 | 
			
		||||
      }
 | 
			
		||||
    >
 | 
			
		||||
      <NWFilterDetails
 | 
			
		||||
        nwfilter={p.nwfilter}
 | 
			
		||||
        editable={true}
 | 
			
		||||
        onChange={valueChanged}
 | 
			
		||||
      />
 | 
			
		||||
    </VirtWebRouteContainer>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@@ -15,7 +15,12 @@ import {
 | 
			
		||||
} from "@mui/material";
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { useNavigate } from "react-router-dom";
 | 
			
		||||
import { NWFilter, NWFilterApi, NWFilterURL } from "../api/NWFilterApi";
 | 
			
		||||
import {
 | 
			
		||||
  NWFilter,
 | 
			
		||||
  NWFilterApi,
 | 
			
		||||
  NWFilterIsBuiltin,
 | 
			
		||||
  NWFilterURL,
 | 
			
		||||
} from "../api/NWFilterApi";
 | 
			
		||||
import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
			
		||||
import { RouterLink } from "../widgets/RouterLink";
 | 
			
		||||
import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer";
 | 
			
		||||
@@ -61,10 +66,7 @@ function NetworkFiltersListRouteInner(p: {
 | 
			
		||||
 | 
			
		||||
    const onlyBuiltin = visibleFilters === VisibleFilters.Builtin;
 | 
			
		||||
 | 
			
		||||
    return p.list.filter(
 | 
			
		||||
      (f) =>
 | 
			
		||||
        ServerApi.Config.builtin_nwfilter_rules.includes(f.name) === onlyBuiltin
 | 
			
		||||
    );
 | 
			
		||||
    return p.list.filter((f) => NWFilterIsBuiltin(f) === onlyBuiltin);
 | 
			
		||||
  }, [visibleFilters]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										65
									
								
								virtweb_frontend/src/routes/ViewNWFilterRoute.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								virtweb_frontend/src/routes/ViewNWFilterRoute.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
			
		||||
import { Button } from "@mui/material";
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { useNavigate, useParams } from "react-router-dom";
 | 
			
		||||
import {
 | 
			
		||||
  NWFilter,
 | 
			
		||||
  NWFilterApi,
 | 
			
		||||
  NWFilterIsBuiltin,
 | 
			
		||||
  NWFilterURL,
 | 
			
		||||
} from "../api/NWFilterApi";
 | 
			
		||||
import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
			
		||||
import { ConfigImportExportButtons } from "../widgets/ConfigImportExportButtons";
 | 
			
		||||
import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer";
 | 
			
		||||
import { NWFilterDetails } from "../widgets/nwfilter/NWFilterDetails";
 | 
			
		||||
 | 
			
		||||
export function ViewNWFilterRoute() {
 | 
			
		||||
  const { uuid } = useParams();
 | 
			
		||||
 | 
			
		||||
  const [nwfilter, setNWFilter] = React.useState<NWFilter | undefined>();
 | 
			
		||||
 | 
			
		||||
  const load = async () => {
 | 
			
		||||
    setNWFilter(await NWFilterApi.GetSingle(uuid!));
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <AsyncWidget
 | 
			
		||||
      loadKey={uuid}
 | 
			
		||||
      ready={nwfilter !== undefined}
 | 
			
		||||
      errMsg="Failed to fetch network filter information!"
 | 
			
		||||
      load={load}
 | 
			
		||||
      build={() => <ViewNetworkFilterRouteInner nwfilter={nwfilter!} />}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function ViewNetworkFilterRouteInner(p: {
 | 
			
		||||
  nwfilter: NWFilter;
 | 
			
		||||
}): React.ReactElement {
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <VirtWebRouteContainer
 | 
			
		||||
      label={`Network filter ${p.nwfilter.name}`}
 | 
			
		||||
      actions={
 | 
			
		||||
        <span style={{ display: "flex", alignItems: "center" }}>
 | 
			
		||||
          <ConfigImportExportButtons
 | 
			
		||||
            filename={`nwfilter-${p.nwfilter.name}.json`}
 | 
			
		||||
            currentConf={p.nwfilter}
 | 
			
		||||
          />
 | 
			
		||||
 | 
			
		||||
          {!NWFilterIsBuiltin(p.nwfilter) && (
 | 
			
		||||
            <Button
 | 
			
		||||
              variant="contained"
 | 
			
		||||
              style={{ marginLeft: "15px" }}
 | 
			
		||||
              onClick={() => navigate(NWFilterURL(p.nwfilter, true))}
 | 
			
		||||
            >
 | 
			
		||||
              Edit
 | 
			
		||||
            </Button>
 | 
			
		||||
          )}
 | 
			
		||||
        </span>
 | 
			
		||||
      }
 | 
			
		||||
    >
 | 
			
		||||
      <NWFilterDetails nwfilter={p.nwfilter} editable={false} />
 | 
			
		||||
    </VirtWebRouteContainer>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								virtweb_frontend/src/widgets/nwfilter/NWFilterDetails.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								virtweb_frontend/src/widgets/nwfilter/NWFilterDetails.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
import { ReactElement } from "react";
 | 
			
		||||
import { NWFilter } from "../../api/NWFilterApi";
 | 
			
		||||
 | 
			
		||||
interface DetailsProps {
 | 
			
		||||
  nwfilter: NWFilter;
 | 
			
		||||
  editable: boolean;
 | 
			
		||||
  onChange?: () => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function NWFilterDetails(p: DetailsProps): ReactElement {
 | 
			
		||||
  return <>todo</>;
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user