diff --git a/src/apps/console/components/console-list-components.tsx b/src/apps/console/components/console-list-components.tsx index 726ca636d..9da28f165 100644 --- a/src/apps/console/components/console-list-components.tsx +++ b/src/apps/console/components/console-list-components.tsx @@ -1,9 +1,6 @@ import {ReactNode, useState} from 'react'; import Tooltip from '~/components/atoms/tooltip'; -import {cn, titleCase} from '~/components/utils'; -import {CopyrightFill, CopySimple} from "@jengaicons/react"; -import useClipboard from "~/lib/client/hooks/use-clipboard"; -import {toast} from "~/components/molecule/toast"; +import {cn} from '~/components/utils'; interface IBase { className?: string; @@ -134,41 +131,27 @@ const ListTitle = ({ }; const ListDomainItem = ({ - data, - value, - }: { + data, + value, + }: { data: ReactNode; value: string; }) => { - const [_, setCopyIcon] = useState(); - const {copy} = useClipboard({ - onSuccess: () => { - setTimeout(() => { - setCopyIcon(); - toast.success(`${titleCase("domain name")} copied successfully`); - }, 1000); - // toast.success('Copied to clipboard'); - }, - }); - return (
{ event.preventDefault() - copy(value); + window.open(`https://${value}`, "_blank") }} - className="flex flex-row gap-md items-center select-none group cursor-pointer" + className="flex flex-row gap-md items-center" >
{data && ( -
+
{data}
)}
- - -
); }; diff --git a/src/apps/console/routes/_main+/$account+/$project+/$environment+/router+/$router+/routes/handle-route.tsx b/src/apps/console/routes/_main+/$account+/$project+/$environment+/router+/$router+/routes/handle-route.tsx index 14fd00d13..78dbcdb47 100644 --- a/src/apps/console/routes/_main+/$account+/$project+/$environment+/router+/$router+/routes/handle-route.tsx +++ b/src/apps/console/routes/_main+/$account+/$project+/$environment+/router+/$router+/routes/handle-route.tsx @@ -1,253 +1,276 @@ /* eslint-disable react/destructuring-assignment */ -import { useParams } from '@remix-run/react'; +import {useParams} from '@remix-run/react'; import Popup from '~/components/molecule/popup'; -import { toast } from '~/components/molecule/toast'; +import {toast} from '~/components/molecule/toast'; import { - ExtractNodeType, - parseName, - parseNodes, + ExtractNodeType, + parseName, + parseNodes, } 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 {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 { handleError } from '~/root/lib/utils/common'; -import { IDialogBase } from '~/console/components/types.d'; -import { IRouters } from '~/console/server/gql/queries/router-queries'; -import { useConsoleApi } from '~/console/server/gql/api-provider'; +import {handleError} from '~/root/lib/utils/common'; +import {IDialogBase} from '~/console/components/types.d'; +import {IRouters} from '~/console/server/gql/queries/router-queries'; +import {useConsoleApi} from '~/console/server/gql/api-provider'; import Select from '~/components/atoms/select'; import useCustomSwr from '~/root/lib/client/hooks/use-custom-swr'; -import { useMapper } from '~/components/utils'; -import { NN } from '~/root/lib/types/common'; -import { TextInput } from '~/components/atoms/input'; -import { useEffect, useState } from 'react'; -import { IApps } from '~/console/server/gql/queries/app-queries'; -import { ModifiedRouter } from './_index'; +import {useMapper} from '~/components/utils'; +import {NN} from '~/root/lib/types/common'; +import {TextInput} from '~/components/atoms/input'; +import {useEffect, useState} from 'react'; +import {IApps} from '~/console/server/gql/queries/app-queries'; +import {ModifiedRouter} from './_index'; +import {Switch} from "~/components/atoms/switch"; type IDialog = IDialogBase< - NN['spec']['routes']>[number] & { id: string } + NN['spec']['routes']>[number] & { id: string } > & { router?: ModifiedRouter }; const Root = (props: IDialog) => { - const { isUpdate, setVisible } = props; - const api = useConsoleApi(); - const reloadPage = useReload(); + const {isUpdate, setVisible} = props; + const api = useConsoleApi(); + const reloadPage = useReload(); - const { project: projectName, environment: envName } = useParams(); - const [selectedApp, setSelectedApp] = useState>(); + const {project: projectName, environment: envName} = useParams(); + const [selectedApp, setSelectedApp] = useState>(); - const { - data, - isLoading: appLoading, - error: appLoadingError, - } = useCustomSwr('/apps', async () => { - if (!projectName || !envName) { - throw new Error('Project and Environment is required!.'); - } - return api.listApps({ projectName, envName }); - }); - - const { values, errors, handleSubmit, handleChange, isLoading, resetValues } = - useForm({ - initialValues: isUpdate - ? { - path: - props.data.path?.[0] === '/' - ? props.data.path.substring(1) - : props.data.path, - app: props.data.app || '', - port: `${props.data.port}`, - } - : { - path: '', - app: '', - port: '', - }, - validationSchema: Yup.object({ - path: Yup.string().test( - 'is-valid', - 'Path should not contain spaces.', - (value) => { - return !value?.includes(' '); - } - ), - app: Yup.string().required(), - port: Yup.string().required(), - }), - - onSubmit: async (val) => { - const { router } = props; - if (!projectName || !envName || !router || !router.metadata?.name) { - throw new Error('Project, Router and Environment is required!.'); + const { + data, + isLoading: appLoading, + error: appLoadingError, + } = useCustomSwr('/apps', async () => { + if (!projectName || !envName) { + throw new Error('Project and Environment is required!.'); } - try { - if (!isUpdate) { - const { errors: e } = await api.updateRouter({ - envName, - projectName, - router: { - displayName: router.displayName, - spec: { - ...router.spec, - routes: [ - ...(router.spec.routes?.map((r) => ({ - path: r.path, - app: r.app, - port: r.port, - })) || []), - { - path: `/${val.path}`, - app: val.app, - port: parseInt(val.port, 10), - }, - ], - }, - metadata: { - ...router.metadata, - }, - }, - }); - if (e) { - throw e[0]; - } - toast.success('Route created successfully'); - } else { - const { errors: e } = await api.updateRouter({ - envName, - projectName, - router: { - displayName: router.displayName, - spec: { - ...router.spec, - routes: [ - ...(router.spec.routes + return api.listApps({projectName, envName}); + }); - ?.filter( - ( - rou // @ts-ignore - ) => rou.id !== props.data.id - ) - .map((route) => ({ - app: route.app, - path: route.path, - port: route.port, - })) || []), - { - path: val.path, - app: val.app, - port: parseInt(val.port, 10), - }, - ], - }, - metadata: { - ...router.metadata, + const {values, errors, handleSubmit, handleChange, isLoading, resetValues} = + useForm({ + initialValues: isUpdate + ? { + path: + props.data.path?.[0] === '/' + ? props.data.path.substring(1) + : props.data.path, + app: props.data.app || '', + port: `${props.data.port}`, + reWrite: false, + } + : { + path: '', + app: '', + port: '', + reWrite: false }, - }, - }); - if (e) { - throw e[0]; + validationSchema: Yup.object({ + path: Yup.string().test( + 'is-valid', + 'Path should not contain spaces.', + (value) => { + return !value?.includes(' '); + } + ), + app: Yup.string().required(), + port: Yup.string().required(), + }), + + onSubmit: async (val) => { + const {router} = props; + if (!projectName || !envName || !router || !router.metadata?.name) { + throw new Error('Project, Router and Environment is required!.'); + } + try { + if (!isUpdate) { + const {errors: e} = await api.updateRouter({ + envName, + projectName, + router: { + displayName: router.displayName, + spec: { + ...router.spec, + routes: [ + ...(router.spec.routes?.map((r) => ({ + path: r.path, + app: r.app, + port: r.port, + })) || []), + { + path: `/${val.path}`, + app: val.app, + port: parseInt(val.port, 10), + }, + ], + }, + metadata: { + ...router.metadata, + }, + }, + }); + if (e) { + throw e[0]; + } + toast.success('Route created successfully'); + } else { + const {errors: e} = await api.updateRouter({ + envName, + projectName, + router: { + displayName: router.displayName, + spec: { + ...router.spec, + routes: [ + ...(router.spec.routes + + ?.filter( + ( + rou // @ts-ignore + ) => rou.id !== props.data.id + ) + .map((route) => ({ + app: route.app, + path: route.path, + port: route.port, + })) || []), + { + path: `/${val.path}`, + app: val.app, + port: parseInt(val.port, 10), + }, + ], + }, + metadata: { + ...router.metadata, + }, + }, + }); + if (e) { + throw e[0]; + } + toast.success('Route updated successfully'); + } + reloadPage(); + setVisible(false); + resetValues(); + } catch (err) { + handleError(err); + } + }, + }); + + const apps = useMapper(parseNodes(data), (val) => ({ + label: val.displayName, + value: parseName(val), + app: val, + render: () => val.displayName, + })); + + useEffect(() => { + const d = parseNodes(data); + if (d.length > 0) { + if (isUpdate) { + setSelectedApp(d.find((app) => parseName(app) === props.data.app)); + } else if (d.length === 1) { + handleChange('app')(dummyEvent(parseName(d[0]))); + setSelectedApp(d[0]); } - toast.success('Route updated successfully'); - } - reloadPage(); - setVisible(false); - resetValues(); - } catch (err) { - handleError(err); } - }, - }); + }, [isUpdate, data]); - const apps = useMapper(parseNodes(data), (val) => ({ - label: val.displayName, - value: parseName(val), - app: val, - render: () => val.displayName, - })); + useEffect(() => { + if (selectedApp?.spec.services?.length === 0) { + handleChange('port')(dummyEvent(selectedApp.spec.services[0].port)); + } + }, [selectedApp]); - useEffect(() => { - const d = parseNodes(data); - if (d.length > 0) { - if (isUpdate) { - setSelectedApp(d.find((app) => parseName(app) === props.data.app)); - } else if (d.length === 1) { - handleChange('app')(dummyEvent(parseName(d[0]))); - setSelectedApp(d[0]); - } - } - }, [isUpdate, data]); + return ( + + - useEffect(() => { - if (selectedApp?.spec.services?.length === 0) { - handleChange('port')(dummyEvent(selectedApp.spec.services[0].port)); - } - }, [selectedApp]); +
+
+
+ { + handleChange('path')(dummyEvent(e.target.value.toLowerCase())); + }} + error={!!errors.path} + message={errors.path} + prefix="/" + /> +
+
- return ( - - - { - handleChange('path')(dummyEvent(e.target.value.toLowerCase())); - }} - error={!!errors.path} - message={errors.path} - prefix="/" - /> - [ - ...(selectedApp?.spec.services?.map((svc) => ({ - label: `${svc.port}`, - value: `${svc.port}`, - })) || []), - ]} - onChange={(val) => { - handleChange('port')(dummyEvent(val.value)); - }} - error={!!errors.port} - message={errors.port} - /> - - - - - - - ); +
+
Rewrite
+
+ { + handleChange('reWrite')(dummyEvent(val)); + }} + /> +
+
+
+ [ + ...(selectedApp?.spec.services?.map((svc) => ({ + label: `${svc.port}`, + value: `${svc.port}`, + })) || []), + ]} + onChange={(val) => { + handleChange('port')(dummyEvent(val.value)); + }} + error={!!errors.port} + message={errors.port} + /> +
+ + + + +
+ ); }; const HandleRoute = (props: IDialog) => { - const { isUpdate, setVisible, visible } = props; - return ( - setVisible(v)}> - {isUpdate ? 'Edit route' : 'Add Route'} - {(!isUpdate || (isUpdate && props.data)) && } - - ); + const {isUpdate, setVisible, visible} = props; + return ( + setVisible(v)}> + {isUpdate ? 'Edit route' : 'Add Route'} + {(!isUpdate || (isUpdate && props.data)) && } + + ); }; export default HandleRoute; 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 a0aad3b01..872ebf7dc 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 @@ -1,11 +1,11 @@ -import { Trash, PencilLine } from '@jengaicons/react'; -import { useState } from 'react'; -import { generateKey, titleCase } from '~/components/utils'; +import {Trash, PencilLine} from '@jengaicons/react'; +import {useState} from 'react'; +import {generateKey, titleCase} from '~/components/utils'; import { - ListItem, - ListTitle, - listClass, - listFlex, ListDomainItem, + ListItem, + ListTitle, + listClass, + listFlex, ListDomainItem, } from '~/console/components/console-list-components'; import DeleteDialog from '~/console/components/delete-dialog'; import Grid from '~/console/components/grid'; @@ -13,18 +13,18 @@ import List from '~/console/components/list'; import ListGridView from '~/console/components/list-grid-view'; import ResourceExtraAction from '~/console/components/resource-extra-action'; import { - ExtractNodeType, - parseName, - parseUpdateOrCreatedBy, - parseUpdateOrCreatedOn, + ExtractNodeType, + parseName, + parseUpdateOrCreatedBy, + parseUpdateOrCreatedOn, } from '~/console/server/r-utils/common'; -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 { useConsoleApi } from '~/console/server/gql/api-provider'; -import { useReload } from '~/root/lib/client/helpers/reloader'; -import { toast } from '~/components/molecule/toast'; +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 {useConsoleApi} from '~/console/server/gql/api-provider'; +import {useReload} from '~/root/lib/client/helpers/reloader'; +import {toast} from '~/components/molecule/toast'; import HandleRouter from './handle-router'; import {CopyButton} from "~/console/components/commons"; @@ -32,234 +32,271 @@ const RESOURCE_NAME = 'domain'; type BaseType = ExtractNodeType; const parseItem = (item: BaseType) => { - return { - name: item.displayName, - id: parseName(item), - updateInfo: { - author: `Updated by ${parseUpdateOrCreatedBy(item)}`, - time: parseUpdateOrCreatedOn(item), - }, - }; + return { + name: item.displayName, + id: parseName(item), + updateInfo: { + author: `Updated by ${parseUpdateOrCreatedBy(item)}`, + time: parseUpdateOrCreatedOn(item), + }, + }; }; type OnAction = ({ - action, - item, -}: { - action: 'edit' | 'delete' | 'detail'; - item: BaseType; + action, + item, + }: { + action: 'edit' | 'delete' | 'detail'; + item: BaseType; }) => void; type IExtraButton = { - onAction: OnAction; - item: BaseType; + onAction: OnAction; + item: BaseType; }; -const ExtraButton = ({ onAction, item }: IExtraButton) => { - return ( - , - type: 'item', - onClick: () => onAction({ action: 'edit', item }), - key: 'edit', - }, - { - label: 'Delete', - icon: , - type: 'item', - onClick: () => onAction({ action: 'delete', item }), - key: 'delete', - className: '!text-text-critical', - }, - ]} - /> - ); +const ExtraButton = ({onAction, item}: IExtraButton) => { + return ( + , + type: 'item', + onClick: () => onAction({action: 'edit', item}), + key: 'edit', + }, + { + label: 'Delete', + icon: , + type: 'item', + onClick: () => onAction({action: 'delete', item}), + key: 'delete', + className: '!text-text-critical', + }, + ]} + /> + ); }; interface IResource { - items: BaseType[]; - onAction: OnAction; + items: BaseType[]; + onAction: OnAction; } -const GridView = ({ items, onAction }: IResource) => { - const { account, project, environment } = useParams(); - return ( - - {items.map((item, index) => { - const { name, id, updateInfo } = parseItem(item); - const keyPrefix = `${RESOURCE_NAME}-${id}-${index}`; - return ( - ( - } - /> - ), - }, - { - key: generateKey(keyPrefix, name), - className: listClass.author, - render: () => ( - item.spec.domains.map((domain) => { - return ( - - ) - }) - ), - }, - { - key: generateKey(keyPrefix, updateInfo.author), - render: () => ( - - ), - }, - ]} - /> - ); - })} - - ); +const GridView = ({items, onAction}: IResource) => { + const {account, project, environment} = useParams(); + return ( + + {items.map((item, index) => { + const {name, id, updateInfo} = parseItem(item); + const keyPrefix = `${RESOURCE_NAME}-${id}-${index}`; + const [extraDomain, setExtraDomain] = useState(false) + return ( + ( + } + /> + ), + }, + { + key: generateKey(keyPrefix, name), + render: () => ( + !extraDomain ? +
+ + {item.spec.domains.length > 1 && ( +
{ + event.preventDefault() + setExtraDomain(true) + }}> + +{item.spec.domains.length - 1} domains +
+ )} +
+ : + item.spec.domains.map((domain) => { + return ( + + ) + }) + ), + }, + { + key: generateKey(keyPrefix, updateInfo.author), + render: () => ( + + ), + }, + ]} + /> + ); + })} +
+ ); }; -const ListView = ({ items, onAction }: IResource) => { - const { account, project, environment } = useParams(); - return ( - - {items.map((item, index) => { - const { name, id, updateInfo } = parseItem(item); - const keyPrefix = `${RESOURCE_NAME}-${id}-${index}`; - const status = listStatus({ key: `${keyPrefix}status`, item }); - return ( - , - }, - status, - { - key: generateKey(keyPrefix, name), - render: () => ( - item.spec.domains.map((domain) => { - return ( - - ) - }) - ), - }, - listFlex({ key: 'flex-1' }), - { - key: generateKey(keyPrefix, updateInfo.author), - className: listClass.author, - render: () => ( - - ), - }, - { - key: generateKey(keyPrefix, 'action'), - render: () => , - }, - ]} - /> - ); - })} - - ); +const ListView = ({items, onAction}: IResource) => { + const {account, project, environment} = useParams(); + return ( + + {items.map((item, index) => { + const {name, id, updateInfo} = parseItem(item); + const keyPrefix = `${RESOURCE_NAME}-${id}-${index}`; + const status = listStatus({key: `${keyPrefix}status`, item}); + const [extraDomain, setExtraDomain] = useState(false) + return ( + , + }, + status, + { + key: generateKey(keyPrefix, name), + render: () => ( + !extraDomain ? +
+ + {item.spec.domains.length > 1 && ( +
{ + event.preventDefault() + setExtraDomain(true) + }}> + +{item.spec.domains.length - 1} domains +
+ )} +
+ : + item.spec.domains.map((domain) => { + return ( + + ) + }) + ), + }, + listFlex({key: 'flex-1'}), + { + key: generateKey(keyPrefix, updateInfo.author), + className: listClass.author, + render: () => ( + + ), + }, + { + key: generateKey(keyPrefix, 'action'), + render: () => , + }, + ]} + /> + ); + })} +
+ ); }; -const RouterResources = ({ items = [] }: { items: BaseType[] }) => { - const [showDeleteDialog, setShowDeleteDialog] = useState( - null - ); - const [visible, setVisible] = useState(null); - const api = useConsoleApi(); - const reloadPage = useReload(); - const { environment, project } = useParams(); - - const props: IResource = { - items, - onAction: ({ action, item }) => { - switch (action) { - case 'edit': - setVisible(item); - break; - case 'delete': - setShowDeleteDialog(item); - break; - default: - } - }, - }; - return ( - <> - } - gridView={} - /> - { - if (!environment || !project) { - throw new Error('Project and Environment is required!.'); - } - try { - const { errors } = await api.deleteRouter({ - envName: environment, - projectName: project, - routerName: parseName(showDeleteDialog), - }); +const RouterResources = ({items = []}: { items: BaseType[] }) => { + const [showDeleteDialog, setShowDeleteDialog] = useState( + null + ); + const [visible, setVisible] = useState(null); + const api = useConsoleApi(); + const reloadPage = useReload(); + const {environment, project} = useParams(); - if (errors) { - throw errors[0]; + const props: IResource = { + items, + onAction: ({action, item}) => { + switch (action) { + case 'edit': + setVisible(item); + break; + case 'delete': + setShowDeleteDialog(item); + break; + default: } - reloadPage(); - toast.success(`${titleCase(RESOURCE_NAME)} deleted successfully`); - setShowDeleteDialog(null); - } catch (err) { - handleError(err); - } - }} - /> - setVisible(null), - }} - /> - - ); + }, + }; + return ( + <> + } + gridView={} + /> + { + if (!environment || !project) { + throw new Error('Project and Environment is required!.'); + } + try { + const {errors} = await api.deleteRouter({ + envName: environment, + projectName: project, + routerName: parseName(showDeleteDialog), + }); + + if (errors) { + throw errors[0]; + } + reloadPage(); + toast.success(`${titleCase(RESOURCE_NAME)} deleted successfully`); + setShowDeleteDialog(null); + } catch (err) { + handleError(err); + } + }} + /> + setVisible(null), + }} + /> + + ); }; export default RouterResources;