diff --git a/gql-queries-generator/doc/queries.graphql b/gql-queries-generator/doc/queries.graphql index 98b5449ca..c4c79b70c 100644 --- a/gql-queries-generator/doc/queries.graphql +++ b/gql-queries-generator/doc/queries.graphql @@ -908,6 +908,16 @@ mutation consoleUpdateApp($projectName: String!, $envName: String!, $app: AppIn! } } +mutation consoleInterceptApp($projectName: String!, $envName: String!, $appname: String!, $deviceName: String!, $intercept: Boolean!) { + core_interceptApp( + projectName: $projectName + envName: $envName + appname: $appname + deviceName: $deviceName + intercept: $intercept + ) +} + mutation consoleDeleteApp($projectName: String!, $envName: String!, $appName: String!) { core_deleteApp(projectName: $projectName, envName: $envName, appName: $appName) } @@ -1065,7 +1075,6 @@ query consoleListApps($projectName: String!, $envName: String!, $search: SearchA displayName enabled environmentName - kind lastUpdatedBy { userEmail userId @@ -1073,11 +1082,12 @@ query consoleListApps($projectName: String!, $envName: String!, $search: SearchA } markedForDeletion metadata { - annotations + generation name namespace } projectName + recordVersion spec { containers { args @@ -1096,23 +1106,6 @@ query consoleListApps($projectName: String!, $envName: String!, $search: SearchA } image imagePullPolicy - livenessProbe { - failureThreshold - httpGet { - httpHeaders - path - port - } - initialDelay - interval - shell { - command - } - tcp { - port - } - type - } name readinessProbe { failureThreshold @@ -1128,15 +1121,6 @@ query consoleListApps($projectName: String!, $envName: String!, $search: SearchA max min } - volumes { - items { - fileName - key - } - mountPath - refName - type - } } displayName freeze @@ -1184,6 +1168,14 @@ query consoleListApps($projectName: String!, $envName: String!, $search: SearchA namespace } } + syncStatus { + action + error + lastSyncedAt + recordVersion + state + syncScheduledAt + } updateTime } } @@ -1243,43 +1235,14 @@ query consoleListRouters($projectName: String!, $envName: String!, $search: Sear } markedForDeletion metadata { + generation name namespace } projectName + recordVersion spec { - backendProtocol - basicAuth { - enabled - secretName - username - } - cors { - allowCredentials - enabled - origins - } domains - https { - clusterIssuer - enabled - forceRedirect - } - ingressClass - maxBodySizeInMB - rateLimit { - connections - enabled - rpm - rps - } - routes { - app - lambda - path - port - rewrite - } } status { checks @@ -1296,6 +1259,14 @@ query consoleListRouters($projectName: String!, $envName: String!, $search: Sear namespace } } + syncStatus { + action + error + lastSyncedAt + recordVersion + state + syncScheduledAt + } updateTime } } @@ -3359,8 +3330,13 @@ mutation consoleUpdateManagedResource($projectName: String!, $envName: String!, } } -query consoleListManagedResources($projectName: String!, $envName: String!) { - core_listManagedResources(projectName: $projectName, envName: $envName) { +query consoleListManagedResources($projectName: String!, $envName: String!, $search: SearchManagedResources, $pq: CursorPaginationIn) { + core_listManagedResources( + projectName: $projectName + envName: $envName + search: $search + pq: $pq + ) { edges { cursor node { @@ -3380,10 +3356,12 @@ query consoleListManagedResources($projectName: String!, $envName: String!) { } markedForDeletion metadata { + generation name namespace } projectName + recordVersion spec { resourceTemplate { apiVersion @@ -3412,6 +3390,14 @@ query consoleListManagedResources($projectName: String!, $envName: String!) { namespace } } + syncStatus { + action + error + lastSyncedAt + recordVersion + state + syncScheduledAt + } updateTime } } @@ -3686,29 +3672,6 @@ query consoleListConsoleVpnDevices($search: CoreSearchVPNDevices, $pq: CursorPag targetPort } } - status { - checks - isReady - lastReadyGeneration - lastReconcileTime - message { - RawMessage - } - resources { - apiVersion - kind - name - namespace - } - } - syncStatus { - action - error - lastSyncedAt - recordVersion - state - syncScheduledAt - } updateTime } } @@ -3754,6 +3717,8 @@ query consoleGetConsoleVpnDevice($name: String!) { query consoleListConsoleVpnDevicesForUser { core_listVPNDevicesForUser { + accountName + apiVersion createdBy { userEmail userId @@ -3762,6 +3727,8 @@ query consoleListConsoleVpnDevicesForUser { creationTime displayName environmentName + id + kind lastUpdatedBy { userEmail userId @@ -3769,7 +3736,11 @@ query consoleListConsoleVpnDevicesForUser { } markedForDeletion metadata { + annotations + creationTimestamp + deletionTimestamp generation + labels name namespace } @@ -3788,30 +3759,11 @@ query consoleListConsoleVpnDevicesForUser { targetPort } } - status { - checks - isReady - lastReadyGeneration - lastReconcileTime - message { - RawMessage - } - resources { - apiVersion - kind - name - namespace - } - } - syncStatus { - action - error - lastSyncedAt - recordVersion - state - syncScheduledAt - } updateTime + wireguardConfig { + encoding + value + } } } diff --git a/src/apps/console/components/name-id-view.tsx b/src/apps/console/components/name-id-view.tsx index 12d57a7e2..625db2559 100644 --- a/src/apps/console/components/name-id-view.tsx +++ b/src/apps/console/components/name-id-view.tsx @@ -108,6 +108,8 @@ export const NameIdView = forwardRef( }, [displayName, name]); const checkNameAvailable = () => { + console.log('inside ', errors); + if (errors) { // onCheckError?.(true); return errors; @@ -174,6 +176,7 @@ export const NameIdView = forwardRef( 'secret', 'project_managed_service', 'console_vpn_device', + 'router', ].includes(tempResType) ? { projectName: project, @@ -217,17 +220,6 @@ export const NameIdView = forwardRef( [displayName, name, isUpdate] ); - useEffect(() => { - console.log( - 'error: ', - (!nameLoading || !isUpdate) && - ((!nameValid && !!name && !nameLoading) || !!errors), - !nameLoading || !isUpdate, - !nameValid && !!name && !nameLoading, - !!errors - ); - }, []); - return ( ( if (!isUpdate) { handleChange?.('name')(dummyEvent(id)); } - if (v) { setNameLoading(true); - handleChange?.(nameErrorLabel)(dummyEvent(true)); + if (!isUpdate) { + handleChange?.(nameErrorLabel)(dummyEvent(true)); + } } else { setNameLoading(false); handleChange?.(nameErrorLabel)(dummyEvent(false)); diff --git a/src/apps/console/components/sync-status.tsx b/src/apps/console/components/sync-status.tsx index 15762dcc8..9e2dbd26c 100644 --- a/src/apps/console/components/sync-status.tsx +++ b/src/apps/console/components/sync-status.tsx @@ -204,7 +204,11 @@ export const listStatus = ({ return { key, className, - render: () => , + render: () => ( +
+ +
+ ), status: parseStatus({ item, type }), }; }; diff --git a/src/apps/console/page-components/handle-console-devices.tsx b/src/apps/console/page-components/handle-console-devices.tsx index c00498340..01b93890d 100644 --- a/src/apps/console/page-components/handle-console-devices.tsx +++ b/src/apps/console/page-components/handle-console-devices.tsx @@ -16,7 +16,7 @@ import { NumberInput } from '~/components/atoms/input'; import { usePagination } from '~/components/molecule/pagination'; import Popup from '~/components/molecule/popup'; import { toast } from '~/components/molecule/toast'; -import { cn } from '~/components/utils'; +import { cn, useMapper } from '~/components/utils'; import List from '~/console/components/list'; import NoResultsFound from '~/console/components/no-results-found'; import QRCode from '~/console/components/qr-code'; @@ -42,8 +42,13 @@ import CodeView from '~/console/components/code-view'; import { InfoLabel } from '~/console/components/commons'; import { parseValue } from '~/console/page-components/util'; import { NameIdView } from '~/console/components/name-id-view'; -import { IConsoleDevices } from '~/console/server/gql/queries/console-vpn-queries'; +import { + IConsoleDevices, + IConsoleDevicesForUser, +} from '~/console/server/gql/queries/console-vpn-queries'; import useCustomSwr from '~/root/lib/client/hooks/use-custom-swr'; +import Select from '~/components/atoms/select'; +import { ConsoleApiType } from '../server/gql/saved-queries'; interface IExposedPorts { targetPort?: number; @@ -269,7 +274,7 @@ export const QRCodeView = ({ data }: { data: string }) => { ); }; -const decodeConfig = ({ +export const decodeConfig = ({ encoding, value, }: { @@ -309,15 +314,12 @@ export const ShowWireguardConfig = ({ const [config, setConfig] = useState(undefined); const api = useConsoleApi(); - const { cluster } = useParams(); - useEffect(() => { if (visible) { (async () => { setLoading(true); try { - const { errors, data: out } = await api.getVpnDevice({ - clusterName: cluster || '', + const { errors, data: out } = await api.getConsoleVpnDevice({ name: data.device, }); if (errors) { @@ -400,13 +402,46 @@ export const ShowWireguardConfig = ({ ); }; +export const switchEnvironment = async ({ + api, + device, + environment, + project, +}: { + api: ConsoleApiType; + device: IConsoleDevicesForUser[number]; + environment: string; + project: string; +}) => { + try { + const { errors } = await api.updateConsoleVpnDevice({ + vpnDevice: { + displayName: device.displayName, + metadata: { + name: parseName(device), + }, + environmentName: environment, + projectName: project, + spec: { + ports: device.spec?.ports, + }, + }, + }); + if (errors) { + throw errors[0]; + } + toast.success('Device switched successfully'); + } catch (err) { + handleError(err); + } +}; + type IDialog = IDialogBase>; const Root = (props: IDialog) => { const { isUpdate, setVisible } = props; const api = useConsoleApi(); const reloadPage = useReload(); - const params = useParams(); const { values, errors, handleChange, handleSubmit, resetValues, isLoading } = useForm({ @@ -416,17 +451,28 @@ const Root = (props: IDialog) => { name: parseName(props.data), ports: props.data.spec?.ports || [], isNameError: false, + projectName: props.data.projectName, + environmentName: props.data.environmentName, } : { displayName: '', name: '', ports: [], isNameError: false, + projectName: '', + environmentName: '', }, - validationSchema: Yup.object({ - name: Yup.string().required(), - displayName: Yup.string().required(), - }), + validationSchema: isUpdate + ? Yup.object({ + name: Yup.string().required(), + displayName: Yup.string().required(), + projectName: Yup.string().required(), + environmentName: Yup.string().required(), + }) + : Yup.object({ + name: Yup.string().required(), + displayName: Yup.string().required(), + }), onSubmit: async (val) => { try { if (!isUpdate) { @@ -435,7 +481,6 @@ const Root = (props: IDialog) => { displayName: val.displayName, metadata: { name: val.name, - namespace: ENV_NAMESPACE, }, spec: { ports: val.ports, @@ -445,14 +490,16 @@ const Root = (props: IDialog) => { if (errors) { throw errors[0]; } + toast.success('Device created successfully'); } else if (isUpdate && props.data) { const { errors } = await api.updateConsoleVpnDevice({ vpnDevice: { displayName: val.displayName, metadata: { name: parseName(props.data), - namespace: ENV_NAMESPACE, }, + environmentName: val.environmentName, + projectName: val.projectName, spec: { ports: val.ports, }, @@ -461,11 +508,9 @@ const Root = (props: IDialog) => { if (errors) { throw errors[0]; } + toast.success('Device updated successfully'); } - reloadPage(); - resetValues(); - toast.success('Device created successfully'); setVisible(false); } catch (err) { handleError(err); @@ -473,27 +518,69 @@ const Root = (props: IDialog) => { }, }); - // const { - // data: projectData, - // error: projectError, - // isLoading: projectIsLoading, - // } = useCustomSwr('/projects', async () => { - // return api.listProjects({}); - // }); + useEffect(() => { + if (!isUpdate) { + resetValues(); + } + }, []); + + const { + data: projectData, + error: projectError, + isLoading: projectIsLoading, + } = useCustomSwr('/projects', async () => { + return api.listProjects({}); + }); + + const { + data: envData, + error: envError, + isLoading: envLoading, + } = useCustomSwr( + () => (values.projectName ? `/environments-${values.projectName}` : null), + async () => { + if (!values.projectName) { + throw new Error('Project name is required!.'); + } + return api.listEnvironments({ + projectName: values.projectName, + }); + } + ); - // const { - // data: projectData, - // error: projectError, - // isLoading: projectIsLoading, - // } = useCustomSwr(()=>`/environments`, async () => { - // return api.listEnvironments({ - // projectName:"" - // }); - // }); + const projects = useMapper(parseNodes(projectData), (val) => ({ + label: val.displayName, + value: parseName(val), + project: val, + render: () => ( +
+
{val.displayName}
+
{parseName(val)}
+
+ ), + })); + + const environments = useMapper(parseNodes(envData), (val) => ({ + label: val.displayName, + value: parseName(val), + project: val, + render: () => ( +
+
{val.displayName}
+
{parseName(val)}
+
+ ), + })); + + useEffect(() => { + console.log('errors here', errors); + }, [errors]); return ( { + console.log('name error', values.isNameError); + if (!values.isNameError) { handleSubmit(e); } else { @@ -514,12 +601,64 @@ const Root = (props: IDialog) => { nameErrorLabel="isNameError" isUpdate={isUpdate} /> - {/* { - handleChange('ports')(dummyEvent(ports)); - }} - /> */} + {isUpdate && ( + <> +
+
+ [...environments]} + value={ + values.environmentName + ? { + label: values.environmentName, + value: values.environmentName, + } + : undefined + } + onChange={(val) => { + handleChange('environmentName')(dummyEvent(val.value)); + }} + /> +
+
+ { + handleChange('ports')(dummyEvent(ports)); + }} + /> + + )} @@ -540,7 +679,7 @@ const HandleConsoleDevices = (props: IDialog) => { ); diff --git a/src/apps/console/routes/_main+/$account+/$project+/$environment+/_layout.tsx b/src/apps/console/routes/_main+/$account+/$project+/$environment+/_layout.tsx index 7d6188eee..6fca5ab34 100644 --- a/src/apps/console/routes/_main+/$account+/$project+/$environment+/_layout.tsx +++ b/src/apps/console/routes/_main+/$account+/$project+/$environment+/_layout.tsx @@ -1,4 +1,14 @@ -import { ChevronDown, Plus, Search } from '@jengaicons/react'; +import { + BackingServices, + ChevronDown, + CirclesFour, + Database, + GearSix, + Plus, + Search, + File, + TreeStructure, +} from '@jengaicons/react'; import { redirect } from '@remix-run/node'; import { Link, @@ -28,6 +38,7 @@ import { useConsoleApi } from '~/console/server/gql/api-provider'; import { BreadcrumButtonContent, BreadcrumSlash, + tabIconSize, } from '~/console/utils/commons'; import { IEnvironment } from '~/console/server/gql/queries/environment-queries'; import { useActivePath } from '~/root/lib/client/hooks/use-active-path'; @@ -51,32 +62,57 @@ const Environment = () => { const tabs = [ { - label: 'Apps', + label: ( + + + Apps + + ), to: '/apps', value: '/apps', }, { - label: 'Routers', + label: ( + + + Router + + ), to: '/routers', value: '/routers', }, { - label: 'Config & Secrets', + label: ( + + + Configs and Secrets + + ), to: '/cs/configs', value: '/cs', }, { - label: 'Managed resources', + label: ( + + + Managed resources + + ), to: '/managed-resources', value: '/managed-resources', }, + // { + // label: 'Jobs & Crons', + // to: '/jc/task', + // value: '/jc', + // }, { - label: 'Jobs & Crons', - to: '/jc/task', - value: '/jc', - }, - { - label: 'Settings', + label: ( + + + Settings + + ), to: '/settings/general', value: '/settings', }, diff --git a/src/apps/console/routes/_main+/$account+/$project+/$environment+/apps/apps-resources.tsx b/src/apps/console/routes/_main+/$account+/$project+/$environment+/apps/apps-resources.tsx index 4a64e0d12..40327bb67 100644 --- a/src/apps/console/routes/_main+/$account+/$project+/$environment+/apps/apps-resources.tsx +++ b/src/apps/console/routes/_main+/$account+/$project+/$environment+/apps/apps-resources.tsx @@ -1,16 +1,28 @@ -import { DotsThreeVerticalFill, GearSix, Trash } from '@jengaicons/react'; -import { Link, useParams } from '@remix-run/react'; +import { + DotsThreeVerticalFill, + GearSix, + LinkBreak, + Trash, + Link as LinkIcon, +} from '@jengaicons/react'; +import { Link, useOutletContext, useParams } from '@remix-run/react'; import { IconButton } from '~/components/atoms/button'; import { cn, generateKey, titleCase } from '~/components/utils'; import { listRender } from '~/console/components/commons'; import { ListItem, + ListSecondary, ListTitle, + listFlex, + listTitleClass, } from '~/console/components/console-list-components'; import Grid from '~/console/components/grid'; import List from '~/console/components/list'; import ListGridView from '~/console/components/list-grid-view'; -import ResourceExtraAction from '~/console/components/resource-extra-action'; +import ResourceExtraAction, { + IResourceExtraItem, +} from '~/console/components/resource-extra-action'; +import { useConsoleApi } from '~/console/server/gql/api-provider'; import { IApps } from '~/console/server/gql/queries/app-queries'; import { ExtractNodeType, @@ -18,13 +30,20 @@ import { parseUpdateOrCreatedBy, parseUpdateOrCreatedOn, } from '~/console/server/r-utils/common'; +import { handleError } from '~/root/lib/utils/common'; +import { toast } from '~/components/molecule/toast'; +import { useReload } from '~/root/lib/client/helpers/reloader'; +import { listStatus } from '~/console/components/sync-status'; +import { IAppContext } from '../app+/$app+/route'; const RESOURCE_NAME = 'app'; +type BaseType = ExtractNodeType; const parseItem = (item: ExtractNodeType) => { return { name: item.displayName, id: parseName(item), + intercept: item.spec.intercept, updateInfo: { author: `Updated by ${titleCase(parseUpdateOrCreatedBy(item))}`, time: parseUpdateOrCreatedOn(item), @@ -32,7 +51,63 @@ const parseItem = (item: ExtractNodeType) => { }; }; -const GridView = ({ items = [] }: { items: ExtractNodeType[] }) => { +type OnAction = ({ + action, + item, +}: { + action: 'delete' | 'edit' | 'intercept' | 'remove_intercept'; + item: BaseType; +}) => void; + +type IExtraButton = { + onAction: OnAction; + item: BaseType; +}; + +const ExtraButton = ({ onAction, item }: IExtraButton) => { + const iconSize = 16; + let options: IResourceExtraItem[] = [ + { + label: 'Settings', + icon: , + type: 'item', + to: `settings/general`, + key: 'settings', + }, + ]; + + if (item.spec.intercept && item.spec.intercept.enabled) { + options = [ + { + label: 'Remove intercept', + icon: , + type: 'item', + onClick: () => onAction({ action: 'remove_intercept', item }), + key: 'remove-intercept', + }, + ...options, + ]; + } else { + options = [ + { + label: 'Intercept', + icon: , + type: 'item', + onClick: () => onAction({ action: 'intercept', item }), + key: 'intercept', + }, + ...options, + ]; + } + return ; +}; + +interface IResource { + items: BaseType[]; + onAction: OnAction; +} + +const GridView = ({ items = [], onAction }: IResource) => { const { account, project, environment } = useParams(); return ( @@ -84,28 +159,42 @@ const GridView = ({ items = [] }: { items: ExtractNodeType[] }) => { ); }; -const ListView = ({ items = [] }: { items: ExtractNodeType[] }) => { +const ListView = ({ items = [], onAction }: IResource) => { const { account, project, environment } = useParams(); - return ( {items.map((item, index) => { - const { name, id, updateInfo } = parseItem(item); + const { name, id, updateInfo, intercept } = parseItem(item); const keyPrefix = `${RESOURCE_NAME}-${id}-${index}`; - const lR = listRender({ keyPrefix, resource: item }); - const statusExtra = lR.statusRender({ className: 'mr-2xl' }); + const status = listStatus({ key: `${keyPrefix}status`, item }); return ( , }, - statusExtra, + status, + // @ts-ignore + ...[ + intercept && !!intercept.enabled + ? { + key: generateKey(keyPrefix, `${name + id}intercept`), + className: listTitleClass, + render: () => ( + + ), + } + : [], + ], + listFlex({ key: 'flex-1' }), { key: generateKey(keyPrefix, updateInfo.author), className: 'w-[180px]', @@ -118,19 +207,7 @@ const ListView = ({ items = [] }: { items: ExtractNodeType[] }) => { }, { key: generateKey(keyPrefix, 'action'), - render: () => ( - , - label: 'Settings', - type: 'item', - }, - ]} - /> - ), + render: () => , }, ]} /> @@ -140,11 +217,55 @@ const ListView = ({ items = [] }: { items: ExtractNodeType[] }) => { ); }; -const AppsResources = ({ items = [] }: { items: ExtractNodeType[] }) => { +const AppsResources = ({ items = [] }: Omit) => { + const api = useConsoleApi(); + const { environment, project } = useParams(); + const { devicesForUser } = useOutletContext(); + const reload = useReload(); + + const interceptApp = async (item: BaseType, intercept: boolean) => { + if (!environment || !project) { + throw new Error('Environment is required!.'); + } + if (devicesForUser && devicesForUser.length > 0) { + const device = devicesForUser[0]; + try { + const { errors } = await api.interceptApp({ + appname: parseName(item), + deviceName: parseName(device), + envName: environment, + intercept, + projectName: project, + }); + + if (errors) { + throw errors[0]; + } + toast.success('App intercepted successfully'); + reload(); + } catch (error) { + handleError(error); + } + } + }; + const props: IResource = { + items, + onAction: ({ action, item }) => { + switch (action) { + case 'intercept': + interceptApp(item, true); + break; + case 'remove_intercept': + interceptApp(item, false); + break; + default: + } + }, + }; return ( } - gridView={} + listView={} + gridView={} /> ); }; diff --git a/src/apps/console/routes/_main+/$account+/$project+/$environment+/apps/route.tsx b/src/apps/console/routes/_main+/$account+/$project+/$environment+/apps/route.tsx index ac20fb349..5016b3f86 100644 --- a/src/apps/console/routes/_main+/$account+/$project+/$environment+/apps/route.tsx +++ b/src/apps/console/routes/_main+/$account+/$project+/$environment+/apps/route.tsx @@ -41,9 +41,9 @@ const Apps = () => { return ( {({ appsData }) => { const apps = parseNodes(appsData); diff --git a/src/apps/console/routes/_main+/$account+/$project+/$environment+/managed-resources/handle-managed-resource.tsx b/src/apps/console/routes/_main+/$account+/$project+/$environment+/managed-resources/handle-managed-resource.tsx new file mode 100644 index 000000000..3ebf34dce --- /dev/null +++ b/src/apps/console/routes/_main+/$account+/$project+/$environment+/managed-resources/handle-managed-resource.tsx @@ -0,0 +1,347 @@ +/* eslint-disable guard-for-in */ +/* eslint-disable react/destructuring-assignment */ +import { useParams } from '@remix-run/react'; +import { useEffect, useRef, useState } from 'react'; +import { NumberInput, TextInput } from '~/components/atoms/input'; +import Popup from '~/components/molecule/popup'; +import { IDialogBase } from '~/console/components/types.d'; +import { useConsoleApi } from '~/console/server/gql/api-provider'; +import { ExtractNodeType, parseName } from '~/console/server/r-utils/common'; +import { useReload } from '~/root/lib/client/helpers/reloader'; +import useForm, { dummyEvent } from '~/root/lib/client/hooks/use-form'; +import Yup from '~/root/lib/server/helpers/yup'; +import { NN } from '~/root/lib/types/common'; +import { handleError } from '~/root/lib/utils/common'; +import { + IMSvTemplate, + IMSvTemplates, +} from '~/console/server/gql/queries/managed-templates-queries'; +import { Switch } from '~/components/atoms/switch'; +import { getManagedTemplate } from '~/console/utils/commons'; +import { NameIdView } from '~/console/components/name-id-view'; +import { IManagedResources } from '~/console/server/gql/queries/managed-resources-queries'; + +type IDialog = IDialogBase> & { + templates: IMSvTemplates; +}; + +type ISelectedService = { + category: { + name: string; + displayName: string; + }; + + service?: NN[number]['items'][number]; +} | null; + +const RenderField = ({ + field, + value, + onChange, + errors, + fieldKey, +}: { + field: NN['service']['fields'][number]; + onChange: (e: string) => (e: { target: { value: any } }) => void; + value: any; + errors: Record; + fieldKey: string; +}) => { + const [qos, setQos] = useState(false); + + useEffect(() => { + if (field.inputType === 'Resource' && value.max === value.min) { + setQos(true); + } + }, []); + + if (field.inputType === 'Number') { + return ( + { + onChange(`res.${field.name}`)( + dummyEvent( + `${parseFloat(target.value) * (field.multiplier || 1)}${ + field.unit + }` + ) + ); + }} + suffix={field.displayUnit} + /> + ); + } + + if (field.inputType === 'String') { + return ( + + ); + } + if (field.inputType === 'Resource') { + return ( +
+
{`${field.label}${ + field.required ? ' *' : '' + }`}
+
+
+
+ { + onChange(`res.${field.name}.min`)( + dummyEvent( + `${parseFloat(target.value) * (field.multiplier || 1)}${ + field.unit + }` + ) + ); + if (qos) { + onChange(`res.${field.name}.max`)( + dummyEvent( + `${parseFloat(target.value) * (field.multiplier || 1)}${ + field.unit + }` + ) + ); + } + }} + suffix={field.displayUnit} + /> +
+ {!qos && ( +
+ { + onChange(`res.${field.name}.max`)( + dummyEvent( + `${parseFloat(target.value) * (field.multiplier || 1)}${ + field.unit + }` + ) + ); + }} + suffix={field.displayUnit} + /> +
+ )} +
+
+ { + setQos(_value); + if (_value) { + onChange(`res.${field.name}.max`)(dummyEvent(`${value.min}`)); + } + }} + /> +
+
+
+ ); + } + return
unknown input type {field.inputType}
; +}; + +type ISelectedResource = IMSvTemplate['resources'][number]; + +const Fill = ({ + selectedResource, + values, + handleChange, + errors, +}: { + selectedResource: ISelectedResource | null | undefined; + values: { [key: string]: any }; + handleChange: (key: string) => (e: { target: { value: any } }) => void; + errors: Record; +}) => { + const nameRef = useRef(null); + useEffect(() => { + nameRef.current?.focus(); + }, [nameRef.current]); + return ( +
+ + {selectedResource?.fields?.map((field) => { + const k = field.name; + const x = k.split('.').reduce((acc, curr) => { + if (!acc) { + return values.res?.[curr]; + } + return acc[curr]; + }, null); + + return ( + + ); + })} +
+ ); +}; + +const Root = (props: IDialog) => { + const { isUpdate, setVisible, templates } = props; + + const api = useConsoleApi(); + const reload = useReload(); + + const { project, environment } = useParams(); + + const { values, errors, handleChange, handleSubmit, resetValues, isLoading } = + useForm({ + initialValues: isUpdate + ? { + name: parseName(props.data), + displayName: props.data.displayName, + isNameError: false, + res: { + ...props.data.spec?.resourceTemplate.spec, + }, + } + : { + name: '', + displayName: '', + res: {}, + isNameError: false, + }, + validationSchema: Yup.object({}), + onSubmit: async (val) => { + if (isUpdate) { + try { + if (!project || !environment) { + throw new Error('Project and environment is required!.'); + } + const { errors: e } = await api.updateManagedResource({ + projectName: project, + envName: environment, + mres: { + displayName: val.displayName, + metadata: { + name: val.name, + }, + + spec: { + resourceTemplate: { + ...props.data.spec.resourceTemplate, + spec: { + ...val.res, + }, + }, + }, + }, + }); + if (e) { + throw e[0]; + } + setVisible(false); + reload(); + } catch (err) { + handleError(err); + } + } + }, + }); + + const getResources = () => { + if (isUpdate) + return getManagedTemplate({ + templates, + apiVersion: props.data.spec?.resourceTemplate.msvcRef.apiVersion || '', + kind: props.data.spec?.resourceTemplate.msvcRef.kind || '', + })?.resources.find( + (rs) => + rs.apiVersion === props.data.spec.resourceTemplate.apiVersion && + rs.kind === props.data.spec.resourceTemplate.kind + ); + return undefined; + }; + + if (!isUpdate) { + return null; + } + return ( + { + if (!values.isNameError) { + handleSubmit(e); + } else { + e.preventDefault(); + } + }} + > + + + + + + + + + ); +}; + +const HandleManagedResources = (props: IDialog) => { + const { isUpdate, setVisible, visible } = props; + return ( + setVisible(v)}> + + {isUpdate ? 'Edit managed service' : 'Add managed service'} + + {(!isUpdate || (isUpdate && props.data)) && } + + ); +}; + +export default HandleManagedResources; diff --git a/src/apps/console/routes/_main+/$account+/$project+/$environment+/managed-resources/managed-resources-resource.tsx b/src/apps/console/routes/_main+/$account+/$project+/$environment+/managed-resources/managed-resources-resource.tsx index a36c4da0f..5ac3a1cbc 100644 --- a/src/apps/console/routes/_main+/$account+/$project+/$environment+/managed-resources/managed-resources-resource.tsx +++ b/src/apps/console/routes/_main+/$account+/$project+/$environment+/managed-resources/managed-resources-resource.tsx @@ -1,8 +1,10 @@ -import { Trash } from '@jengaicons/react'; +import { PencilSimple, Trash } from '@jengaicons/react'; import { generateKey, titleCase } from '~/components/utils'; import { ListItem, ListTitle, + listFlex, + listTitleClass, } from '~/console/components/console-list-components'; import Grid from '~/console/components/grid'; import List from '~/console/components/list'; @@ -23,6 +25,8 @@ import { handleError } from '~/root/lib/utils/common'; import { toast } from '~/components/molecule/toast'; import { useParams } from '@remix-run/react'; import { IManagedResources } from '~/console/server/gql/queries/managed-resources-queries'; +import { listStatus } from '~/console/components/sync-status'; +import HandleManagedResources from './handle-managed-resource'; const RESOURCE_NAME = 'managed resource'; type BaseType = ExtractNodeType; @@ -47,7 +51,7 @@ type OnAction = ({ action, item, }: { - action: 'delete'; + action: 'delete' | 'edit'; item: BaseType; }) => void; @@ -60,6 +64,13 @@ const ExtraButton = ({ onAction, item }: IExtraButton) => { return ( , + type: 'item', + onClick: () => onAction({ action: 'edit', item }), + key: 'edit', + }, { label: 'Delete', icon: , @@ -122,7 +133,7 @@ const ListView = ({ items = [], templates = [], onAction }: IResource) => { {items.map((item, index) => { const { name, id, updateInfo } = parseItem(item, templates); const keyPrefix = `${RESOURCE_NAME}-${id}-${index}`; - + const status = listStatus({ key: `${keyPrefix}status`, item }); return ( { columns={[ { key: generateKey(keyPrefix, name), - className: 'flex-1 min-w-[200px]', + className: listTitleClass, render: () => , }, + status, + listFlex({ key: 'flex-1' }), { key: generateKey(keyPrefix, 'author'), className: 'w-[180px]', @@ -165,6 +178,7 @@ const ManagedResourceResources = ({ const [showDeleteDialog, setShowDeleteDialog] = useState( null ); + const [visible, setVisible] = useState(null); const api = useConsoleApi(); const reloadPage = useReload(); const params = useParams(); @@ -177,6 +191,9 @@ const ManagedResourceResources = ({ case 'delete': setShowDeleteDialog(item); break; + case 'edit': + setVisible(item); + break; default: break; } @@ -215,6 +232,15 @@ const ManagedResourceResources = ({ } }} /> + setVisible(null), + data: visible!, + templates: templates || [], + }} + /> ); }; diff --git a/src/apps/console/routes/_main+/$account+/$project+/$environment+/new-app/app-dialogs.tsx b/src/apps/console/routes/_main+/$account+/$project+/$environment+/new-app/app-dialogs.tsx index 12d739974..845920823 100644 --- a/src/apps/console/routes/_main+/$account+/$project+/$environment+/new-app/app-dialogs.tsx +++ b/src/apps/console/routes/_main+/$account+/$project+/$environment+/new-app/app-dialogs.tsx @@ -310,9 +310,9 @@ const AppDialog = ({ const sC = selectedConfig; reset(); onSubmit({ - refKey: parseName(sC), - refName: sK, - type: 'config', + refKey: sK, + refName: parseName(sC), + type: show?.type as ICSComponent['type'], }); } }} diff --git a/src/apps/console/routes/_main+/$account+/$project+/$environment+/new-app/app-environment-variables.tsx b/src/apps/console/routes/_main+/$account+/$project+/$environment+/new-app/app-environment-variables.tsx index ae5ee85bf..c5f8ba2e3 100644 --- a/src/apps/console/routes/_main+/$account+/$project+/$environment+/new-app/app-environment-variables.tsx +++ b/src/apps/console/routes/_main+/$account+/$project+/$environment+/new-app/app-environment-variables.tsx @@ -182,12 +182,12 @@ export const EnvironmentVariables = () => { type: Yup.string().oneOf(['config', 'secret']).notRequired(), key: Yup.string().required(), - value: Yup.string().when(['type'], ([type], schema) => { - if (type === undefined) { - return schema.required(); - } - return schema; - }), + // value: Yup.string().when(['type'], ([type], schema) => { + // if (type === undefined) { + // return schema.required(); + // } + // return schema; + // }), refKey: Yup.string() .when(['type'], ([type], schema) => { if (type === 'config' || type === 'secret') { @@ -211,9 +211,10 @@ export const EnvironmentVariables = () => { values, setValues, submit, + errors, resetValues: resetAppValue, } = useForm({ - initialValues: getContainer().env, + initialValues: getContainer().env || [], validationSchema: Yup.array(entry), onSubmit: (val) => { setContainer((c) => ({ @@ -222,7 +223,14 @@ export const EnvironmentVariables = () => { })); }, }); + + useEffect(() => { + console.log('errors ', errors); + }, [errors]); + useEffect(() => { + console.log('here values: ', values); + submit(); }, [values]); @@ -234,7 +242,7 @@ export const EnvironmentVariables = () => { }, [hasChanges]); const addEntry = (val: IEnvVariable) => { - setValues((v) => { + setValues((v = []) => { const data = { key: val.key, type: val.type, @@ -242,12 +250,7 @@ export const EnvironmentVariables = () => { refKey: val.refKey || '', value: val.value || '', }; - if (v) { - v?.push(data); - } else { - return [data]; - } - return v; + return [...v, data]; }); }; @@ -301,6 +304,8 @@ export const EnvironmentVariables = () => { }), }), onSubmit: () => { + console.log(eValues); + if (eValues.textInputValue) { const ev: IEnvVariable = { key: eValues.key, @@ -317,7 +322,7 @@ export const EnvironmentVariables = () => { refKey: eValues.value.refKey, refName: eValues.value.refName, type: eValues.value.type, - value: undefined, + value: '', }; // setEnvVariables((prev) => [...prev, ev]); @@ -444,6 +449,8 @@ export const EnvironmentVariables = () => { show={showCSDialog} setShow={setShowCSDialog} onSubmit={(item) => { + console.log('items', item); + eSetValues((v) => { return { ...v, diff --git a/src/apps/console/routes/_main+/$account+/$project+/$environment+/new-managed-resource/_index.tsx b/src/apps/console/routes/_main+/$account+/$project+/$environment+/new-managed-resource/_index.tsx index 510248ced..1285ee80e 100644 --- a/src/apps/console/routes/_main+/$account+/$project+/$environment+/new-managed-resource/_index.tsx +++ b/src/apps/console/routes/_main+/$account+/$project+/$environment+/new-managed-resource/_index.tsx @@ -14,12 +14,18 @@ import ProgressWrapper from '~/console/components/progress-wrapper'; import { useConsoleApi } from '~/console/server/gql/api-provider'; import useForm, { dummyEvent } from '~/root/lib/client/hooks/use-form'; import Yup from '~/root/lib/server/helpers/yup'; -import { FormEventHandler, useEffect, useState } from 'react'; +import { + Dispatch, + FormEventHandler, + SetStateAction, + useEffect, + useState, +} from 'react'; import { IMSvTemplate } from '~/console/server/gql/queries/managed-templates-queries'; import { Switch } from '~/components/atoms/switch'; import { NumberInput, TextInput } from '~/components/atoms/input'; import { handleError } from '~/root/lib/utils/common'; -import { useMapper } from '~/components/utils'; +import { titleCase, useMapper } from '~/components/utils'; import { IRemixCtx } from '~/root/lib/types/common'; import { LoadingComp, pWrapper } from '~/console/components/loading-component'; import { GQLServerHandler } from '~/console/server/gql/saved-queries'; @@ -184,8 +190,6 @@ const RenderField = ({ }; const flatM = (obj: Record) => { - console.log('obj', obj); - const flatJson = {}; for (const key in obj) { const parts = key.split('.'); @@ -248,28 +252,9 @@ const TemplateView = ({ isLoading, }: ITemplateView) => { return ( -
{ - if (!values.isNameError) { - handleSubmit(e); - } else { - e.preventDefault(); - } - }} - > +
Create your managed services.
- { options={async () => [...domains]} onChange={(val) => { setSelectedDomains(val); + handleChange('domains')(dummyEvent([...val.map((v) => v.value)])); }} - error={!!domainError || !!domainLoadingError} - message={domainLoadingError ? 'Error fetching domains.' : domainError} + error={!!errors.domains || !!domainLoadingError} + message={ + errors.domains || + (domainLoadingError ? 'Error fetching domains.' : '') + } loading={domainLoading} + disableWhileLoading /> @@ -162,8 +210,8 @@ const HandleRouter = (props: IDialog) => { return ( ); diff --git a/src/apps/console/routes/_main+/$account+/$project+/$environment+/routers/router-resources.tsx b/src/apps/console/routes/_main+/$account+/$project+/$environment+/routers/router-resources.tsx index 9d632f918..bdb4c66ce 100644 --- a/src/apps/console/routes/_main+/$account+/$project+/$environment+/routers/router-resources.tsx +++ b/src/apps/console/routes/_main+/$account+/$project+/$environment+/routers/router-resources.tsx @@ -6,6 +6,8 @@ import { ListBody, ListItem, ListTitle, + listFlex, + listTitleClass, } from '~/console/components/console-list-components'; import DeleteDialog from '~/console/components/delete-dialog'; import Grid from '~/console/components/grid'; @@ -23,6 +25,7 @@ import { useReload } from '~/root/lib/client/helpers/reloader'; import { handleError } from '~/root/lib/utils/common'; import { IRouters } from '~/console/server/gql/queries/router-queries'; import { Link, useParams } from '@remix-run/react'; +import { listStatus } from '~/console/components/sync-status'; import HandleRouter from './handle-router'; const RESOURCE_NAME = 'domain'; @@ -126,6 +129,7 @@ const ListView = ({ items, onAction }: IResource) => { {items.map((item, index) => { const { name, id, updateInfo } = parseItem(item); const keyPrefix = `${RESOURCE_NAME}-${id}-${index}`; + const status = listStatus({ key: `${keyPrefix}status`, item }); return ( { columns={[ { key: generateKey(keyPrefix, name + id), - className: 'flex-1', + className: listTitleClass, render: () => , }, + status, + listFlex({ key: 'flex-1' }), { key: generateKey(keyPrefix, updateInfo.author), className: 'w-[180px]', diff --git a/src/apps/console/routes/_main+/$account+/$project+/_layout.tsx b/src/apps/console/routes/_main+/$account+/$project+/_layout.tsx index c1f52e68f..0b189b6e8 100644 --- a/src/apps/console/routes/_main+/$account+/$project+/_layout.tsx +++ b/src/apps/console/routes/_main+/$account+/$project+/_layout.tsx @@ -97,7 +97,7 @@ const LocalBreadcrum = ({
> & { templates: IMSvTemplates; @@ -36,35 +37,42 @@ type ISelectedService = { displayName: string; }; - service: NN[number]['items'][number]; + service?: NN[number]['items'][number]; } | null; const RenderField = ({ field, value, onChange, - error, - message, + errors, + fieldKey, }: { field: NN['service']['fields'][number]; onChange: (e: string) => (e: { target: { value: any } }) => void; value: any; - error: boolean; - message?: string; + errors: { + [key: string]: string | undefined; + }; + fieldKey: string; }) => { const [qos, setQos] = useState(false); + + useEffect(() => { + if (field.inputType === 'Resource' && value.max === value.min) { + setQos(true); + } + }, []); + if (field.inputType === 'Number') { return ( { - onChange(`${field.name}`)( + onChange(`res.${field.name}`)( dummyEvent( `${parseFloat(target.value) * (field.multiplier || 1)}${ field.unit @@ -81,9 +89,11 @@ const RenderField = ({ return ( ); } @@ -97,14 +107,12 @@ const RenderField = ({
{ - onChange(`${field.name}.min`)( + onChange(`res.${field.name}.min`)( dummyEvent( `${parseFloat(target.value) * (field.multiplier || 1)}${ field.unit @@ -112,7 +120,7 @@ const RenderField = ({ ) ); if (qos) { - onChange(`${field.name}.max`)( + onChange(`res.${field.name}.max`)( dummyEvent( `${parseFloat(target.value) * (field.multiplier || 1)}${ field.unit @@ -127,14 +135,12 @@ const RenderField = ({ {!qos && (
{ - onChange(`${field.name}.max`)( + onChange(`res.${field.name}.max`)( dummyEvent( `${parseFloat(target.value) * (field.multiplier || 1)}${ field.unit @@ -154,7 +160,7 @@ const RenderField = ({ onChange={(_value) => { setQos(_value); if (_value) { - onChange(`${field.name}.max`)(dummyEvent(`${value.min}`)); + onChange(`res.${field.name}.max`)(dummyEvent(`${value.min}`)); } }} /> @@ -179,39 +185,47 @@ const Fill = ({ [key: string]: string | undefined; }; }) => { + const nameRef = useRef(null); + useEffect(() => { + nameRef.current?.focus(); + }, [nameRef.current]); return (
- - { - handleChange('name')(dummyEvent(v)); - }} - /> - {selectedService?.service.fields.map((field) => { + + {selectedService?.service?.fields.map((field) => { const k = field.name; const x = k.split('.').reduce((acc, curr) => { + console.log(acc, curr, values); + if (!acc) { - return values[curr]; + return values.res[curr]; } + return acc[curr]; }, null); + console.log('x', x); + return ( ); })} @@ -219,29 +233,6 @@ const Fill = ({ ); }; -const flatM = (obj: Record) => { - const flatJson = {}; - for (const key in obj) { - const parts = key.split('.'); - let temp: Record = flatJson; - - parts.forEach((part, index) => { - if (index === parts.length - 1) { - temp[part] = { - min: null, - max: null, - }; - } else { - temp[part] = temp[part] || {}; - } - temp = temp[part]; - }); - } - console.log('flatjson ', flatJson); - - return flatJson; -}; - const Root = (props: IDialog) => { const { isUpdate, setVisible, templates } = props; const [selectedService, setSelectedService] = @@ -253,78 +244,66 @@ const Root = (props: IDialog) => { const { project } = useParams(); const [step, setStep] = useState<'choose' | 'fill'>('choose'); - const { - values, - errors, - handleChange, - handleSubmit, - resetValues, - isLoading, - setValues, - } = useForm({ - initialValues: isUpdate - ? { - name: '', - displayName: '', - res: { - ...props.data.spec?.msvcSpec.serviceTemplate.spec, - }, - } - : { - name: '', - displayName: '', - res: {}, - }, - validationSchema: Yup.object({}), - onSubmit: async (val) => { - const tempVal = { ...val }; - // @ts-ignore - delete tempVal.name; - // @ts-ignore - delete tempVal.displayName; - - try { - if (!project) { - throw new Error('Project is required!.'); - } - if ( - !selectedService?.service.apiVersion || - !selectedService.service.kind - ) { - throw new Error('Service apiversion or kind error.'); - } - const { errors: e } = await api.createProjectMSv({ - projectName: project, - pmsvc: { - displayName: val.displayName, - metadata: { - name: val.name, + const { values, errors, handleChange, handleSubmit, resetValues, isLoading } = + useForm({ + initialValues: isUpdate + ? { + name: parseName(props.data), + displayName: props.data.displayName, + isNameError: false, + res: { + ...props.data.spec?.msvcSpec.serviceTemplate.spec, }, + } + : { + name: '', + displayName: '', + res: {}, + isNameError: false, + }, + validationSchema: Yup.object({}), + onSubmit: async (val) => { + if (isUpdate) { + try { + if (!project) { + throw new Error('Project is required!.'); + } + const { errors: e } = await api.updateProjectMSv({ + projectName: project, + pmsvc: { + displayName: val.displayName, + metadata: { + name: val.name, + }, - spec: { - msvcSpec: { - serviceTemplate: { - apiVersion: selectedService.service.apiVersion, - kind: selectedService.service.kind, - spec: { - ...tempVal, + spec: { + msvcSpec: { + serviceTemplate: { + apiVersion: + props.data.spec?.msvcSpec.serviceTemplate.apiVersion || + '', + kind: + props.data.spec?.msvcSpec.serviceTemplate.kind || '', + spec: { + ...val.res, + }, + }, }, + targetNamespace: '', }, }, - targetNamespace: '', - }, - }, - }); - if (e) { - throw e[0]; + }); + if (e) { + throw e[0]; + } + setVisible(false); + reload(); + } catch (err) { + handleError(err); + } } - setVisible(false); - reload(); - } catch (err) { - handleError(err); - } - }, - }); + }, + }); const getService = () => { if (isUpdate) @@ -333,25 +312,33 @@ const Root = (props: IDialog) => { apiVersion: props.data.spec?.msvcSpec.serviceTemplate.apiVersion || '', kind: props.data.spec?.msvcSpec.serviceTemplate.kind || '', }); - return null; + return undefined; }; - if (!isUpdate && getService()) { + + if (!isUpdate) { return null; } return ( { - if (step === 'choose') { - setStep('fill'); - e.preventDefault(); - } else handleSubmit(e); + console.log('name error..', values.isNameError); + + handleSubmit(e); + // if (!values.isNameError) { + // handleSubmit(e); + // } else { + // e.preventDefault(); + // } }} > { /> - {step === 'fill' ? ( - { - resetValues({}); - setSelectedService(null); - setStep('choose'); - }} - content="Back" - /> - ) : null} + @@ -386,11 +361,7 @@ const Root = (props: IDialog) => { const HandleBackendService = (props: IDialog) => { const { isUpdate, setVisible, visible } = props; return ( - setVisible(v)} - > + setVisible(v)}> {isUpdate ? 'Edit managed service' : 'Add managed service'} diff --git a/src/apps/console/routes/_main+/$account+/_layout.tsx b/src/apps/console/routes/_main+/$account+/_layout.tsx index b905670f5..c022cb950 100644 --- a/src/apps/console/routes/_main+/$account+/_layout.tsx +++ b/src/apps/console/routes/_main+/$account+/_layout.tsx @@ -1,7 +1,12 @@ import { + ArrowCounterClockwise, + ArrowsCounterClockwise, CaretDownFill, ChevronUpDown, + Copy, + GearSix, Plus, + QrCode, WireGuardlogo, } from '@jengaicons/react'; import { redirect } from '@remix-run/node'; @@ -32,9 +37,19 @@ import MenuSelect from '~/console/components/menu-select'; import { BreadcrumButtonContent } from '~/console/utils/commons'; import OptionList from '~/components/atoms/option-list'; import { IConsoleDevicesForUser } from '~/console/server/gql/queries/console-vpn-queries'; -import { Button } from '~/components/atoms/button'; -import HandleConsoleDevices from '~/console/page-components/handle-console-devices'; +import { Button, IconButton } from '~/components/atoms/button'; +import HandleConsoleDevices, { + QRCodeView, + ShowWireguardConfig, + decodeConfig, + switchEnvironment, +} from '~/console/page-components/handle-console-devices'; import Profile from '~/components/molecule/profile'; +import useClipboard from '~/root/lib/client/hooks/use-clipboard'; +import { useConsoleApi } from '~/console/server/gql/api-provider'; +import { handleError } from '~/root/lib/utils/common'; +import { toast } from '~/components/molecule/toast'; +import { useReload } from '~/root/lib/client/helpers/reloader'; import { IConsoleRootContext } from '../_layout/_layout'; const AccountMenu = ({ account }: { account: IAccount }) => { @@ -64,7 +79,7 @@ const AccountMenu = ({ account }: { account: IAccount }) => { }; const Account = () => { - const { account } = useLoaderData(); + const { account, devicesForUser } = useLoaderData(); const rootContext = useOutletContext(); const { unloadState, reset, proceed } = useUnsavedChanges(); @@ -74,7 +89,7 @@ const Account = () => { }, []); return ( <> - + { @@ -101,9 +116,20 @@ const Account = () => { }; const DevicesMenu = ({ devices }: { devices: IConsoleDevicesForUser }) => { - const d = devices; - const [visible, setVisible] = useState(false); + const [isUpdate, setIsUpdate] = useState(false); + const [showQR, setShowQR] = useState(false); + + const { copy } = useClipboard({ + onSuccess: () => { + toast.success('WG config copied successfully'); + }, + }); + const api = useConsoleApi(); + const reload = useReload(); + + const { environment, project } = useParams(); + if (!devices || devices?.length === 0) { return (
@@ -125,42 +151,144 @@ const DevicesMenu = ({ devices }: { devices: IConsoleDevicesForUser }) => {
); } + + const device = devices[0]; + + const getConfig = async () => { + try { + const { errors, data: out } = await api.getConsoleVpnDevice({ + name: parseName(device), + }); + if (errors) { + throw errors[0]; + } + if (out.wireguardConfig) copy(decodeConfig(out.wireguardConfig)); + } catch (error) { + handleError(error); + } + }; return ( devices?.length > 0 && ( - - -