diff --git a/src/components/modules/networkDescription.tsx b/src/components/modules/networkDescription.tsx index 4e24789a..89fcf335 100644 --- a/src/components/modules/networkDescription.tsx +++ b/src/components/modules/networkDescription.tsx @@ -1,15 +1,53 @@ import { useState, useEffect } from "react"; import React from "react"; -import { api } from "~/utils/api"; import { useRouter } from "next/router"; import { useTranslations } from "next-intl"; +import { type RouterInputs, type RouterOutputs, api } from "~/utils/api"; +import { + type QueryClient, + useQueryClient, + type Query, +} from "@tanstack/react-query"; +import { type CentralNetwork } from "~/types/central/network"; +import { type NetworkEntity } from "~/types/local/network"; interface IProp { central?: boolean; } - +const updateCache = ({ + client, + data, + input, +}: { + client: QueryClient; + input: RouterInputs["network"]["getNetworkById"]; + data: NetworkEntity | Partial; +}) => { + client.setQueryData( + [ + ["network", "getNetworkById"], + { + input, + type: "query", + }, + ], + (oldData) => { + const newData = oldData as Query< + RouterOutputs["network"]["getNetworkById"] + >; + if ("network" in newData && newData.network && typeof data === "object") { + return { + ...newData, + network: { ...(newData.network as object), ...(data as object) }, + }; + } + return newData; + } + ); +}; const NetworkDescription = ({ central = false }: IProp) => { const t = useTranslations("networkById"); + const client = useQueryClient(); const { query } = useRouter(); const [state, setState] = useState({ toggleDescriptionInput: false, @@ -34,7 +72,16 @@ const NetworkDescription = ({ central = false }: IProp) => { }, [networkById?.network?.description]); const { mutate: networkDescription } = - api.network.networkDescription.useMutation(); + api.network.networkDescription.useMutation({ + onSuccess: (data) => { + const input = { + nwid: query.id as string, + central, + }; + // void refecthNetworkById(); + updateCache({ client, data, input }); + }, + }); const toggleDescriptionInput = () => { setState({ diff --git a/src/components/modules/networkFlowRules.tsx b/src/components/modules/networkFlowRules.tsx index 963f0202..5d2b063d 100644 --- a/src/components/modules/networkFlowRules.tsx +++ b/src/components/modules/networkFlowRules.tsx @@ -27,6 +27,7 @@ export const NetworkFlowRules = () => { const { mutate: updateFlowRoute } = api.network.setFlowRule.useMutation({ onSuccess: () => { void fetchFlowRoute({ nwid: query.id as string, reset: false }); + void toast.success("Flow rules updated successfully"); }, onError: ({ message }) => { try { @@ -81,7 +82,7 @@ export const NetworkFlowRules = () => { return (
Flow Rules
@@ -104,8 +105,10 @@ export const NetworkFlowRules = () => { + +
+ + + ); +}; diff --git a/src/pages/central/[id].tsx b/src/pages/central/[id].tsx index abe51073..6031b018 100644 --- a/src/pages/central/[id].tsx +++ b/src/pages/central/[id].tsx @@ -12,7 +12,7 @@ import CopyIcon from "~/icons/copy"; import toast from "react-hot-toast"; // import { DeletedNetworkMembersTable } from "~/components/modules/deletedNetworkMembersTable"; import { useModalStore } from "~/utils/store"; -import { NetworkFlowRules } from "~/components/modules/networkFlowRules"; +import { CentralFlowRules } from "~/components/modules/ztCentral/centralFlowRules"; import { NetworkDns } from "~/components/modules/networkDns"; import { NetworkMulticast } from "~/components/modules/networkMulticast"; import cn from "classnames"; @@ -237,7 +237,7 @@ const CentralNetworkById = () => {
- +
Network Actions diff --git a/src/pages/central/index.tsx b/src/pages/central/index.tsx index 4f760834..d7dd376d 100644 --- a/src/pages/central/index.tsx +++ b/src/pages/central/index.tsx @@ -23,12 +23,17 @@ const CentralNetworks: NextPageWithLayout = () => { createNetwork(null, { onSuccess: () => void refetch() }); }; + const title = `${globalSiteTitle} - ${t("title")}`; if (isLoading) { - return
{t("loading")}
; + // add loading progress bar to center of page, vertially and horizontally + return ( +
+

+ +

+
+ ); } - - const title = `${globalSiteTitle} - ${t("title")}`; - return ( <> diff --git a/src/pages/network/index.tsx b/src/pages/network/index.tsx index 3d067a66..83b5d496 100644 --- a/src/pages/network/index.tsx +++ b/src/pages/network/index.tsx @@ -23,11 +23,17 @@ const Networks: NextPageWithLayout = () => { }; if (isLoading) { - return
{t("loading")}
; + // add loading progress bar to center of page, vertially and horizontally + return ( +
+

+ +

+
+ ); } const title = `${globalSiteTitle} - ${t("title")}`; - console.log(userNetworks); return ( <> diff --git a/src/server/api/routers/networkRouter.ts b/src/server/api/routers/networkRouter.ts index 82f44565..3d074871 100644 --- a/src/server/api/routers/networkRouter.ts +++ b/src/server/api/routers/networkRouter.ts @@ -15,14 +15,16 @@ import RuleCompiler from "~/utils/rule-compiler"; import { throwError, type APIError } from "~/server/helpers/errorHandler"; import { createTransporter, inviteUserTemplate, sendEmail } from "~/utils/mail"; import ejs from "ejs"; -import { type NetworkEntity } from "~/types/local/network"; +import { type TagsByName, type NetworkEntity } from "~/types/local/network"; import { type NetworkAndMemberResponse } from "~/types/network"; import { + type CapabilitiesByName, type MemberEntity, type Paths, type Peers, } from "~/types/local/member"; import { type FlattenCentralMembers } from "~/types/central/members"; +import { type CentralNetwork } from "~/types/central/network"; const customConfig: Config = { dictionaries: [adjectives, animals], @@ -402,10 +404,13 @@ export const networkRouter = createTRPCRouter({ central: input.central, updateParams, }); - // console.log(updated); - const { config, ...otherProps } = updated?.network || {}; - return { ...config, ...otherProps }; + if (input.central) { + const { id: nwid, config, ...otherProps } = updated as CentralNetwork; + return { nwid, ...config, ...otherProps } as Partial; + } else { + return updated as NetworkEntity; + } }), networkName: protectedProcedure .input( @@ -423,11 +428,18 @@ export const networkRouter = createTRPCRouter({ : { ...input.updateParams }; // if central is true, send the request to the central API and return the response - return ztController.network_update({ + const updated = await ztController.network_update({ nwid: input.nwid, central: input.central, updateParams, }); + + if (input.central) { + const { id: nwid, config, ...otherProps } = updated as CentralNetwork; + return { nwid, ...config, ...otherProps } as Partial; + } else { + return updated as NetworkEntity; + } }), networkDescription: protectedProcedure .input( @@ -442,20 +454,29 @@ export const networkRouter = createTRPCRouter({ .mutation(async ({ ctx, input }) => { // if central is true, send the request to the central API and return the response if (input.central) { - return ztController.network_update({ + const updated = await ztController.network_update({ nwid: input.nwid, central: input.central, updateParams: input.updateParams, }); + + const { description } = updated as CentralNetwork; + return { + description, + }; } // Update network in prisma as description is not part of the local controller network object. - return await ctx.prisma.network.update({ + const updated = await ctx.prisma.network.update({ where: { nwid: input.nwid }, data: { ...input.updateParams, }, }); + + return { + description: updated.description, + }; }), dns: protectedProcedure .input( @@ -586,15 +607,19 @@ export const networkRouter = createTRPCRouter({ .input( z.object({ nwid: z.string().nonempty(), - flowRoute: z.string().nonempty(), + central: z.boolean().default(false), + updateParams: z.object({ + flowRoute: z.string().nonempty(), + }), }) ) .mutation(async ({ ctx, input }) => { - const { flowRoute } = input; + const { flowRoute } = input.updateParams; const rules = []; + const caps: Record = {}; - const tags: TagsByName = {}; + const tags: Record = {}; // try { const err = RuleCompiler(flowRoute, rules, caps, tags) as string[]; if (err) { @@ -637,21 +662,36 @@ export const networkRouter = createTRPCRouter({ // 2 // ) // ); - // update zerotier network with the new flow route - await ztController.network_update(input.nwid, { + + const updateObj = { rules, capabilities: capsArray, tags: tagsArray, - capabilitiesByName: capabilitiesByName, - tagsByName: tags, + }; + + const updateParams = input.central + ? { + config: { ...updateObj }, + capabilitiesByName, + tagsByName: tags, + rulesSource: flowRoute, + } + : { ...updateObj, capabilitiesByName, tagsByName: tags }; + + // update zerotier network with the new flow route + const updatedRules = await ztController.network_update({ + nwid: input.nwid, + central: input.central, + updateParams, }); - // update network in prisma + if (input.central) return updatedRules; + // update network in prisma const { prisma } = ctx; // Start a transaction - await prisma.$transaction([ + return await prisma.$transaction([ // Update network prisma.network.update({ where: { nwid: input.nwid }, @@ -668,7 +708,8 @@ export const networkRouter = createTRPCRouter({ .input( z.object({ nwid: z.string().nonempty(), - reset: z.boolean().optional(), + central: z.boolean().default(false), + reset: z.boolean().default(false).optional(), }) ) .mutation(async ({ ctx, input }) => { @@ -715,11 +756,15 @@ drop # Accept anything else. This is required since default is 'drop'. accept;`; + if (input.central || input.reset) { + return DEFAULT_NETWORK_ROUTE_CONFIG; + } + const flow = await ctx.prisma.network.findFirst({ where: { nwid: input.nwid }, }); - if (!flow.flowRule || input.reset) { + if (!flow.flowRule) { return DEFAULT_NETWORK_ROUTE_CONFIG; } diff --git a/src/types/central/network.d.ts b/src/types/central/network.d.ts index f1d24f51..cb0c9b9a 100644 --- a/src/types/central/network.d.ts +++ b/src/types/central/network.d.ts @@ -8,11 +8,12 @@ interface NetworkBase { authorizedMemberCount: number; totalMemberCount: number; capabilitiesByName: CapabilitiesByName; + tagsByName?: TagsByName; } interface NetworkConfig { - authTokens: null; - creationTime: number; + authTokens?: null; + creationTime?: number; capabilities: Capability[]; enableBroadcast: boolean; id: string; @@ -29,7 +30,7 @@ interface NetworkConfig { tags: Tag[]; v4AssignMode: { zt: boolean }; v6AssignMode: { "6plane": boolean; rfc4193: boolean; zt: boolean }; - dns: { domain: string; servers: string[] }; + dns: { domain?: string; servers?: string[] }; ssoConfig: { enabled: boolean; mode: string }; } @@ -80,7 +81,9 @@ export interface CentralNetwork extends NetworkBase { config: Partial; } -export interface FlattenCentralNetwork extends NetworkBase, NetworkConfig { +export interface FlattenCentralNetwork + extends NetworkBase, + Partial { cidr?: string[]; - nwid: string; + nwid?: string; } diff --git a/src/types/local/network.d.ts b/src/types/local/network.d.ts index 3bc1fd86..c3246ffc 100644 --- a/src/types/local/network.d.ts +++ b/src/types/local/network.d.ts @@ -28,8 +28,13 @@ export interface NetworkEntity { rulesSource?: string; ssoEnabled?: boolean; cidr?: string[]; + tagsByName?: TagsByName; } +interface TagsByName { + [tagName: string]: TagDetails; + id?: number; +} export interface RoutesEntity { target?: string | null; via?: string | null; diff --git a/src/utils/ztApi.ts b/src/utils/ztApi.ts index 9542784d..772b2e68 100644 --- a/src/utils/ztApi.ts +++ b/src/utils/ztApi.ts @@ -94,23 +94,18 @@ const flattenCentralMembers = (members: CentralMembers[]) => { }); }; -export const flattenNetworks = ( - networks: CentralNetwork[] | CentralNetwork -): Partial | Partial => { - // Single object input - if (!Array.isArray(networks)) { - const { id: nwid, config, ...otherProps } = networks; - const flattenedNetwork = { nwid, ...config, ...otherProps }; - return flattenedNetwork; - } +export const flattenNetwork = ( + network: CentralNetwork +): FlattenCentralNetwork => { + const { id: nwid, config, ...otherProps } = network; + const flattenedNetwork = { nwid, ...config, ...otherProps }; + return flattenedNetwork; +}; - // Array input - if (!networks) return []; - return networks.map((network) => { - const { id: nwid, config, ...otherProps } = network; - const flattenedNetwork = { nwid, ...config, ...otherProps }; - return flattenedNetwork; - }); +export const flattenNetworks = ( + networks: CentralNetwork[] +): FlattenCentralNetwork[] => { + return networks.map((network) => flattenNetwork(network)); }; /* @@ -136,6 +131,7 @@ const postData = async ( ): Promise => { try { const { data } = await axios.post(addr, payload, headers); + return data; } catch (error) { const message = `An error occurred while posting data to ${addr}`; @@ -257,7 +253,7 @@ export const network_create = async ( headers, {} ); - return flattenNetworks([data]); + return flattenNetwork(data); } else { const controllerStatus = (await get_controller_status( isCentral @@ -429,16 +425,15 @@ export const network_update = async function ({ // get headers based on local or central api const headers = await getOptions(central); + try { - const updated = await postData( + return await postData( addr, headers, payload ); - - return { network: { ...updated } }; } catch (error) { - const prefix = isCentral ? "[CENTRAL] " : ""; + const prefix = central ? "[CENTRAL] " : ""; const message = `${prefix}An error occurred while getting network_update`; throw new APIError(message, error as AxiosError); } @@ -462,7 +457,7 @@ export const member_delete = async ({ const response: AxiosResponse = await axios.delete(addr, headers); return response.status as MemberDeleteResponse; } catch (error) { - const prefix = isCentral ? "[CENTRAL] " : ""; + const prefix = central ? "[CENTRAL] " : ""; const message = `${prefix}An error occurred while getting member_delete`; throw new APIError(message, error as AxiosError); } @@ -486,7 +481,7 @@ export const member_update = async ( const response: AxiosResponse = await axios.post(addr, data, headers); return response.data as ZTControllerMemberUpdate; } catch (error) { - const prefix = isCentral ? "[CENTRAL] " : ""; + const prefix = central ? "[CENTRAL] " : ""; const message = `${prefix}An error occurred while getting member_update`; throw new APIError(message, error as AxiosError); } @@ -504,8 +499,7 @@ export const peers = async (): Promise => { const response: AxiosResponse = await axios.get(addr, headers); return response.data as ZTControllerGetPeer; } catch (error) { - const prefix = isCentral ? "[CENTRAL] " : ""; - const message = `${prefix}An error occurred while getting peers`; + const message = `An error occurred while getting peers`; throw new APIError(message, error as AxiosError); } };