From f08f86d03bd9841b9d685b2fa3642dc1c7578171 Mon Sep 17 00:00:00 2001 From: Bikash Date: Mon, 11 Mar 2024 18:50:47 +0530 Subject: [PATCH 1/6] WIP sync status --- gql-queries-generator/doc/queries.graphql | 10 + src/apps/console/components/commons.tsx | 11 - src/apps/console/components/icons.tsx | 8 + src/apps/console/components/listV2.tsx | 14 +- src/apps/console/components/sync-status.tsx | 380 +++++++++++++++++- .../$a+/$cloudprovider+/validate-cp.tsx | 3 +- .../$environment+/apps/apps-resources-v2.tsx | 335 +++++++++++++++ .../$environment+/apps/apps-resources.tsx | 20 +- .../$project+/$environment+/apps/route.tsx | 10 +- .../$cluster+/storage/storage-resources.tsx | 1 - .../infra+/clusters/cluster-resources-v2.tsx | 246 ++++++++++++ .../infra+/clusters/cluster-resources.tsx | 1 - .../$account+/infra+/clusters/route.tsx | 4 +- .../console/server/gql/queries/app-queries.ts | 24 +- src/generated/gql/server.ts | 6 + 15 files changed, 1019 insertions(+), 54 deletions(-) create mode 100644 src/apps/console/routes/_main+/$account+/$project+/$environment+/apps/apps-resources-v2.tsx create mode 100644 src/apps/console/routes/_main+/$account+/infra+/clusters/cluster-resources-v2.tsx diff --git a/gql-queries-generator/doc/queries.graphql b/gql-queries-generator/doc/queries.graphql index cde388390..ee30bdcae 100644 --- a/gql-queries-generator/doc/queries.graphql +++ b/gql-queries-generator/doc/queries.graphql @@ -1028,6 +1028,11 @@ query consoleGetApp($projectName: String!, $envName: String!, $name: String!) { } } status { + checkList { + description + title + name + } checks isReady lastReadyGeneration @@ -1157,6 +1162,11 @@ query consoleListApps($projectName: String!, $envName: String!, $search: SearchA name namespace } + checkList { + description + title + name + } } syncStatus { action diff --git a/src/apps/console/components/commons.tsx b/src/apps/console/components/commons.tsx index 30764ecd3..f243b659a 100644 --- a/src/apps/console/components/commons.tsx +++ b/src/apps/console/components/commons.tsx @@ -88,21 +88,10 @@ export const CopyButton = ({ setTimeout(() => { setCopyIcon(); }, 1000); - // toast.success('Copied to clipboard'); }, }); return ( - // { - // copy(value); - // setCopyIcon(); - // }} - // />
{ copy(value); diff --git a/src/apps/console/components/icons.tsx b/src/apps/console/components/icons.tsx index 6bd74f5c6..5e07083cd 100644 --- a/src/apps/console/components/icons.tsx +++ b/src/apps/console/components/icons.tsx @@ -39,4 +39,12 @@ export { BackingServices, VirtualMachine, Database, + ArrowsClockwise, + Info, + Fan, + WarningCircle, + ChecksFill as Checks, + CircleNotch, + Circle, + CircleFill, } from '@jengaicons/react'; diff --git a/src/apps/console/components/listV2.tsx b/src/apps/console/components/listV2.tsx index 29d256311..93603fcd0 100644 --- a/src/apps/console/components/listV2.tsx +++ b/src/apps/console/components/listV2.tsx @@ -104,6 +104,7 @@ interface IMain { interface IRowBase extends IMain { linkComponent?: any; headers?: IHeader[]; + disabled?: boolean; } const RowBase = ({ @@ -115,6 +116,7 @@ const RowBase = ({ pressed = false, plain, headers, + disabled, }: IRowBase) => { let Component: any = linkComponent; @@ -138,12 +140,13 @@ const RowBase = ({ { 'bg-surface-basic-default': !pressed, 'cursor-pointer hover:bg-surface-basic-hovered': - (!!onClick || linkComponent !== 'div') && !pressed, + (!!onClick || linkComponent !== 'div') && !pressed && !disabled, 'bg-surface-basic-active': pressed, + 'cursor-default': !!disabled, } ); - if (!!onClick || linkComponent !== 'div') { + if (!disabled) { return ( ; to?: string }>; + rows: Array<{ + columns: Record; + to?: string; + disabled?: boolean; + }>; className?: Array; }; headerClassName?: string; @@ -279,6 +286,7 @@ const Root = ({ columns={r.columns} to={r.to} headers={data.headers} + disabled={r.disabled} /> ))}
diff --git a/src/apps/console/components/sync-status.tsx b/src/apps/console/components/sync-status.tsx index f2ac6dbdf..aefaae066 100644 --- a/src/apps/console/components/sync-status.tsx +++ b/src/apps/console/components/sync-status.tsx @@ -2,15 +2,23 @@ import { ArrowsClockwise, Trash, Info, - Warning, WarningCircle, -} from '@jengaicons/react'; + CheckCircleFill, + Checks, + CircleNotch, + XCircleFill, + Circle, + CircleFill, + X, +} from '~/console/components/icons'; import Tooltip from '~/components/atoms/tooltip'; import { titleCase } from '~/components/utils'; import { - Github__Com___Kloudlite___Api___Pkg___Types__SyncState as SyncState, - Github__Com___Kloudlite___Api___Pkg___Types__SyncAction as SyncAction, + Github__Com___Kloudlite___Api___Pkg___Types__SyncState as ISyncState, + Github__Com___Kloudlite___Api___Pkg___Types__SyncAction as ISyncAction, + Github__Com___Kloudlite___Operator___Pkg___Operator__CheckMetaIn as ICheckList, } from '~/root/src/generated/gql/server'; +import { Badge } from '~/components/atoms/badge'; interface IStatusMeta { metadata?: { generation: number }; @@ -30,15 +38,369 @@ interface IStatusMeta { }>; }; syncStatus: { - action: SyncAction; + action: ISyncAction; error?: string; lastSyncedAt?: any; recordVersion: number; - state: SyncState; + state: ISyncState; syncScheduledAt?: any; }; } +interface IStatusMetaV2 { + metadata?: { generation: number }; + recordVersion: number; + markedForDeletion?: boolean; + status?: { + checks?: any; + isReady: boolean; + lastReadyGeneration?: number; + lastReconcileTime?: any; + message?: { RawMessage?: any }; + resources?: Array<{ + apiVersion: string; + kind: string; + name: string; + namespace: string; + }>; + checkList?: ICheckList[]; + }; + syncStatus: { + action: ISyncAction; + error?: string; + lastSyncedAt?: any; + recordVersion: number; + state: ISyncState; + syncScheduledAt?: any; + }; +} + +const Stage = { + Waiting: 'yet-to-be-reconciled', + InProgress: 'under-reconcilation', + Error: 'errored-during-reconcilation', + Completed: 'finished-reconcilation', +}; + +type OverallStates = 'idle' | 'in-progress' | 'error' | 'ready' | 'deleting'; + +const State = ({ state }: { state: OverallStates }) => { + switch (state) { + case 'ready': + return ( + } type="info"> + Ready + + ); + case 'in-progress': + return ( + + + + + + + } + type="warning" + > + In progress + + ); + case 'deleting': + return 'Deleting'; + case 'error': + return ( + } type="critical"> + Error + + ); + default: + return ( + } type="warning"> + Waiting + + ); + } +}; + +export const SyncStatusV2 = ({ item }: { item: IStatusMetaV2 }) => { + const { status } = item; + + const parseStage = (check: OverallStates) => { + const iconSize = 12; + + switch (check) { + case 'in-progress': + return { + icon: ( + + + + ), + }; + case 'error': + return { + icon: ( + + + + ), + }; + case 'ready': + return { + icon: ( + + + + ), + }; + case 'idle': + default: + return { + icon: ( + + + + ), + }; + } + }; + + const parseOverallState = (s: typeof status): OverallStates => { + /* + WaitingState State = "yet-to-be-reconciled" + RunningState State = "under-reconcilation" + ErroredState State = "errored-during-reconcilation" + CompletedState State = "finished-reconcilation" + */ + + const checks: { + [key: string]: { + error: string; + generation: number; + message: string; + state: + | 'yet-to-be-reconciled' + | 'under-reconcilation' + | 'errored-during-reconcilation' + | 'finished-reconcilation'; + status: boolean; + }; + } = s?.checks; + if (!checks) { + return 'idle'; + } + + /* + if no one stared -> idle + + if any fail all fail + + if any not in progress -> in progress + + if all done -> done + */ + + const mainStatus = status?.checkList?.reduce( + (acc, curr) => { + const k = checks[curr.name]; + if (acc.progress === 'done') { + return acc; + } + + if (k) { + if (acc.value === 'idle' && k.state === 'yet-to-be-reconciled') { + return { + value: 'idle', + progress: 'done', + }; + } + + if (k.state === 'under-reconcilation') { + return { + value: 'in-progress', + progress: 'done', + }; + } + + if (k.state === 'errored-during-reconcilation') { + return { + value: 'error', + progress: 'done', + }; + } + + if (k.state === 'finished-reconcilation') { + return { + value: 'ready', + progress: 'init', + }; + } + } + + return acc; + }, + { + value: 'idle', + progress: 'init', + } + ); + + return (mainStatus?.value as OverallStates) || 'idle'; + }; + + const getProgressItems = (s: typeof status) => { + const checks: { + [key: string]: { + error: string; + generation: number; + message: string; + state: + | 'yet-to-be-reconciled' + | 'under-reconcilation' + | 'errored-during-reconcilation' + | 'finished-reconcilation'; + status: boolean; + }; + } = s?.checks; + if (!checks) { + return 'idle'; + } + + /* + if no one stared -> idle + + if any fail all fail + + if any not in progress -> in progress + + if all done -> done + */ + + const items = status?.checkList?.reduce( + (acc, curr) => { + const k = checks[curr.name]; + if (acc.progress === 'done') { + acc.items.push({ + ...curr, + result: 'idle', + }); + return acc; + } + + const res = (() => { + if (k) { + if (acc.value === 'idle' && k.state === 'yet-to-be-reconciled') { + return { + value: 'idle', + progress: 'done', + }; + } + + if (k.state === 'under-reconcilation') { + return { + value: 'in-progress', + progress: 'done', + }; + } + + if (k.state === 'errored-during-reconcilation') { + return { + value: 'error', + progress: 'done', + }; + } + + if (k.state === 'finished-reconcilation') { + return { + value: 'ready', + progress: 'init', + }; + } + } + + return acc; + })(); + + acc.items.push({ + ...curr, + result: res?.value, + }); + + acc.value = res.value; + acc.progress = res.progress; + + return acc; + }, + { + value: 'idle', + items: [], + progress: 'init', + } + ); + + return items?.items; + }; + + const data = { + checks: { + 'deployment-svc-and-hpa-created': { + state: 'under-reconcilation', + status: true, + }, + 'deployment-ready': { + state: 'finished-reconcilation', + status: false, + }, + }, + checkList: [ + { + title: 'Scaling configured', + name: 'deployment-svc-and-hpa-created', + }, + { + title: 'Deployment ready', + name: 'deployment-ready', + }, + ], + }; + + const k = parseOverallState(data); + + const ic = getProgressItems(data); + console.log(ic); + return ( +
+ +
+
+ {ic.map((cl) => ( +
+ {parseStage(cl.name).icon} + {cl.title} +
+ ))} +
+
+ } + > +
+ +
+
+
+ ); +}; + const SyncStatus = ({ item }: { item: IStatusMeta }) => { const statusIconSize = 16; @@ -191,19 +553,13 @@ export const parseStatus = ({ }; export const listStatus = ({ - key, - className, item, type, }: { - key: string; - className?: string; item: IStatusMeta; type?: IResourceType; }) => { return { - key, - className, render: () => (
diff --git a/src/apps/console/routes/_a+/onboarding+/$a+/$cloudprovider+/validate-cp.tsx b/src/apps/console/routes/_a+/onboarding+/$a+/$cloudprovider+/validate-cp.tsx index 195aa16d1..ebe2614df 100644 --- a/src/apps/console/routes/_a+/onboarding+/$a+/$cloudprovider+/validate-cp.tsx +++ b/src/apps/console/routes/_a+/onboarding+/$a+/$cloudprovider+/validate-cp.tsx @@ -267,8 +267,9 @@ const Validator = ({ cloudProvider }: { cloudProvider: any }) => { data?.result ? undefined : { - variant: 'primary', + variant: 'outline', content: 'Skip', + prefix: undefined, onClick: () => { navigate( `/onboarding/${parseName(account)}/${parseName( diff --git a/src/apps/console/routes/_main+/$account+/$project+/$environment+/apps/apps-resources-v2.tsx b/src/apps/console/routes/_main+/$account+/$project+/$environment+/apps/apps-resources-v2.tsx new file mode 100644 index 000000000..7d2a4524a --- /dev/null +++ b/src/apps/console/routes/_main+/$account+/$project+/$environment+/apps/apps-resources-v2.tsx @@ -0,0 +1,335 @@ +import { + GearSix, + LinkBreak, + Link as LinkIcon, + Repeat, +} from '@jengaicons/react'; +import { Link, useOutletContext, useParams } from '@remix-run/react'; +import { generateKey, titleCase } from '~/components/utils'; +import { + ListItem, + ListSecondary, + ListTitle, + listClass, + listFlex, +} 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, { + 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, + parseName, + parseName as pn, + 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 { SyncStatusV2, listStatus } from '~/console/components/sync-status'; +import { useWatchReload } from '~/lib/client/helpers/socket/useWatch'; +import ListV2 from '~/console/components/listV2'; +import { IEnvironmentContext } from '../_layout'; + +const RESOURCE_NAME = 'app'; +type BaseType = ExtractNodeType; + +const parseItem = (item: ExtractNodeType) => { + return { + name: item.displayName, + id: pn(item), + intercept: item.spec.intercept, + updateInfo: { + author: `Updated by ${titleCase(parseUpdateOrCreatedBy(item))}`, + time: parseUpdateOrCreatedOn(item), + }, + }; +}; + +type OnAction = ({ + action, + item, +}: { + action: 'delete' | 'edit' | 'intercept' | 'remove_intercept' | 'restart'; + item: BaseType; +}) => void; + +type IExtraButton = { + onAction: OnAction; + item: BaseType; +}; + +const ExtraButton = ({ onAction, item }: IExtraButton) => { + const { account, project, environment } = useParams(); + const iconSize = 16; + let options: IResourceExtraItem[] = [ + { + label: 'Settings', + icon: , + type: 'item', + to: `/${account}/${project}/${environment}/app/${parseName( + item + )}/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, + ]; + } + + options = [ + { + label: 'Restart', + icon: , + type: 'item', + onClick: () => onAction({ action: 'restart', item }), + key: 'restart', + }, + ...options, + ]; + + return ; +}; + +interface IResource { + 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 ( + ( + , + label: 'Settings', + type: 'item', + }, + ]} + /> + } + /> + ), + }, + { + key: generateKey(keyPrefix, updateInfo.author), + render: () => ( + + ), + }, + ]} + /> + ); + })} + + ); +}; + +const ListView = ({ items = [], onAction }: IResource) => { + const { account, project, environment } = useParams(); + return ( + 'Name', + name: 'name', + className: 'w-[180px]', + }, + { + render: () => 'Status', + name: 'status', + className: 'flex-1 min-w-[30px] flex items-center justify-center', + }, + { + render: () => 'Updated', + name: 'updated', + className: 'w-[180px]', + }, + { + render: () => '', + name: 'action', + className: 'w-[24px]', + }, + ], + rows: items.map((i) => { + const { name, id, updateInfo } = parseItem(i); + return { + columns: { + name: { + render: () => , + }, + status: { + render: () => ( +
+ +
+ ), + }, + // provider: { render: () => }, + updated: { + render: () => ( + + ), + }, + action: { + render: () => , + }, + }, + to: `/${account}/infra/${id}/overview`, + disabled: true, + }; + }), + }} + /> + ); +}; + +const AppsResourcesV2 = ({ items = [] }: Omit) => { + const api = useConsoleApi(); + const { environment, project, account } = useParams(); + const { devicesForUser } = useOutletContext(); + const reload = useReload(); + + useWatchReload( + items.map((i) => { + return `account:${account}.project:${project}.environment:${environment}.app:${parseName( + i + )}`; + }) + ); + + // useWatchItems(items, (item) => ({ + // account, + // project, + // environment, + // app: pn(item), + // })); + + 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: pn(item), + deviceName: pn(device), + envName: environment, + intercept, + projectName: project, + }); + + if (errors) { + throw errors[0]; + } + toast.success('App intercepted successfully'); + reload(); + } catch (error) { + handleError(error); + } + } + }; + + const restartApp = async (item: BaseType) => { + if (!environment || !project) { + throw new Error('Environment is required!.'); + } + + try { + const { errors } = await api.restartApp({ + appName: pn(item), + envName: environment, + projectName: project, + }); + + if (errors) { + throw errors[0]; + } + toast.success('App restarted successfully'); + // reload(); + } catch (error) { + handleError(error); + } + }; + + const props: IResource = { + items, + onAction: ({ action, item }) => { + switch (action) { + case 'intercept': + interceptApp(item, true); + break; + case 'restart': + restartApp(item); + break; + case 'remove_intercept': + interceptApp(item, false); + break; + default: + } + }, + }; + return ( + } + gridView={} + /> + ); +}; + +export default AppsResourcesV2; 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 53dd6ade7..12bbdcf4a 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 @@ -32,8 +32,6 @@ 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 { IAccountContext } from '~/console/routes/_main+/$account+/_layout'; -import { IProjectContext } from '~/console/routes/_main+/$account+/$project+/_layout'; import { useWatchReload } from '~/lib/client/helpers/socket/useWatch'; import { IEnvironmentContext } from '../_layout'; @@ -199,15 +197,15 @@ const ListView = ({ items = [], onAction }: IResource) => { ...[ intercept && !!intercept.enabled ? { - key: generateKey(keyPrefix, `${name + id}intercept`), - className: listClass.title, - render: () => ( - - ), - } + key: generateKey(keyPrefix, `${name + id}intercept`), + className: listClass.title, + render: () => ( + + ), + } : [], ], listFlex({ key: 'flex-1' }), 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 a62eb7f54..d9679b792 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 @@ -12,8 +12,8 @@ import { IRemixCtx } from '~/root/lib/types/common'; import fake from '~/root/fake-data-generator/fake'; import { clearAppState } from '~/console/page-components/app-states'; import { useEffect } from 'react'; -import AppsResources from './apps-resources'; import Tools from './tools'; +import AppsResourcesV2 from './apps-resources-v2'; export const loader = async (ctx: IRemixCtx) => { ensureAccountSet(ctx); @@ -47,9 +47,9 @@ const Apps = () => { return ( {({ appsData }) => { const apps = parseNodes(appsData); @@ -87,7 +87,7 @@ const Apps = () => { }} tools={} > - +
); diff --git a/src/apps/console/routes/_main+/$account+/infra+/$cluster+/storage/storage-resources.tsx b/src/apps/console/routes/_main+/$account+/infra+/$cluster+/storage/storage-resources.tsx index b9203062e..078d120d0 100644 --- a/src/apps/console/routes/_main+/$account+/infra+/$cluster+/storage/storage-resources.tsx +++ b/src/apps/console/routes/_main+/$account+/infra+/$cluster+/storage/storage-resources.tsx @@ -23,7 +23,6 @@ import { toast } from '~/components/molecule/toast'; import { useReload } from '~/root/lib/client/helpers/reloader'; import { handleError } from '~/root/lib/utils/common'; import { IAccountContext } from '~/console/routes/_main+/$account+/_layout'; -import { IClusterContext } from '~/console/routes/_main+/$account+/infra+/$cluster+/_layout'; import { useWatchReload } from '~/lib/client/helpers/socket/useWatch'; const RESOURCE_NAME = 'storage'; diff --git a/src/apps/console/routes/_main+/$account+/infra+/clusters/cluster-resources-v2.tsx b/src/apps/console/routes/_main+/$account+/infra+/clusters/cluster-resources-v2.tsx new file mode 100644 index 000000000..7ce59610e --- /dev/null +++ b/src/apps/console/routes/_main+/$account+/infra+/clusters/cluster-resources-v2.tsx @@ -0,0 +1,246 @@ +import { GearSix } from '@jengaicons/react'; +import { Link, useOutletContext, useParams } from '@remix-run/react'; +import { cn, generateKey, titleCase } from '~/components/utils'; +import { listRender } from '~/console/components/commons'; +import ConsoleAvatar from '~/console/components/console-avatar'; +import { + ListBody, + ListItem, + ListTitle, +} from '~/console/components/console-list-components'; +import Grid from '~/console/components/grid'; +import ListGridView from '~/console/components/list-grid-view'; +import ResourceExtraAction from '~/console/components/resource-extra-action'; +import { IStatus, listStatus } from '~/console/components/sync-status'; +import { IClusters } from '~/console/server/gql/queries/cluster-queries'; +import { + ExtractNodeType, + parseName, + parseUpdateOrCreatedBy, + parseUpdateOrCreatedOn, +} from '~/console/server/r-utils/common'; +import { renderCloudProvider } from '~/console/utils/commons'; +import logger from '~/root/lib/client/helpers/log'; +import { IAccountContext } from '~/console/routes/_main+/$account+/_layout'; +import { useWatchReload } from '~/lib/client/helpers/socket/useWatch'; +import ListV2 from '~/console/components/listV2'; + +type BaseType = ExtractNodeType; +const RESOURCE_NAME = 'cluster'; + +const getProvider = (item: BaseType) => { + if (!item.spec) { + return ''; + } + switch (item.spec.cloudProvider) { + case 'aws': + return ( +
+ {renderCloudProvider({ cloudprovider: item.spec.cloudProvider })} + ({item.spec.aws?.region}) +
+ ); + case 'gcp': + case 'azure': + return ( +
+ {item.spec.cloudProvider} +
+ ); + + default: + logger.error('unknown provider', item.spec.cloudProvider); + return ''; + } +}; + +const parseItem = (item: BaseType) => { + return { + name: item.displayName, + id: parseName(item), + provider: getProvider(item), + updateInfo: { + author: `Updated by ${titleCase(parseUpdateOrCreatedBy(item))}`, + time: parseUpdateOrCreatedOn(item), + }, + }; +}; + +const ExtraButton = ({ + cluster, + status, +}: { + cluster: BaseType; + status: IStatus; +}) => { + const { account } = useParams(); + return ( + , + type: 'item', + to: `/${account}/infra/${cluster.metadata.name}/settings`, + key: 'settings', + }, + ]} + /> + ); +}; + +const GridView = ({ items }: { items: BaseType[] }) => { + const { account } = useParams(); + return ( + + {items.map((item, index) => { + const { name, id, provider, updateInfo } = parseItem(item); + const keyPrefix = `${RESOURCE_NAME}-${id}-${index}`; + const lR = listRender({ keyPrefix, resource: item }); + const status = lR.statusRender({ className: '' }); + return ( + ( + + + } + /> + ), + }, + { + key: generateKey(keyPrefix, id + name + provider), + render: () => ( +
+ {/* */} + +
+ ), + }, + status, + { + key: generateKey(keyPrefix, updateInfo.author), + render: () => ( + + ), + }, + ]} + /> + ); + })} +
+ ); +}; +const ListView = ({ items }: { items: BaseType[] }) => { + const { account } = useParams(); + return ( + ( +
+ + Name +
+ ), + name: 'name', + className: 'w-[180px]', + }, + { + render: () => '', + name: 'status', + className: 'flex-1 min-w-[30px] flex items-center justify-center', + }, + { + render: () => 'Provider (Region)', + name: 'provider', + className: 'w-[180px]', + }, + { + render: () => 'Updated', + name: 'updated', + className: 'w-[180px]', + }, + { + render: () => '', + name: 'action', + className: 'w-[24px]', + }, + ], + rows: items.map((i) => { + const { name, id, updateInfo, provider } = parseItem(i); + + const tempStatus = listStatus({ + item: i, + }); + return { + columns: { + name: { + render: () => ( + } + /> + ), + }, + status: { + render: () => ( +
{tempStatus.render()}
+ ), + }, + provider: { render: () => }, + updated: { + render: () => ( + + ), + }, + action: { + render: () => ( + + ), + }, + }, + to: `/${account}/infra/${id}/overview`, + disabled: true, + }; + }), + }} + /> + ); +}; + +const ClusterResourcesV2 = ({ items = [] }: { items: BaseType[] }) => { + const { account } = useOutletContext(); + useWatchReload( + items.map((i) => { + return `account:${parseName(account)}.cluster:${parseName(i)}`; + }) + ); + + return ( + } + listView={} + /> + ); +}; + +export default ClusterResourcesV2; diff --git a/src/apps/console/routes/_main+/$account+/infra+/clusters/cluster-resources.tsx b/src/apps/console/routes/_main+/$account+/infra+/clusters/cluster-resources.tsx index abfd64b98..2aad085ef 100644 --- a/src/apps/console/routes/_main+/$account+/infra+/clusters/cluster-resources.tsx +++ b/src/apps/console/routes/_main+/$account+/infra+/clusters/cluster-resources.tsx @@ -267,7 +267,6 @@ const ListView = ({ items }: IResource) => { const statusRender = lR.statusRender({ className: 'min-w-[80px] mx-[25px] basis-full text-center', }); - return ( { const promise = pWrapper(async () => { @@ -152,7 +152,7 @@ const Clusters = () => { }} tools={} > - + ); }} diff --git a/src/apps/console/server/gql/queries/app-queries.ts b/src/apps/console/server/gql/queries/app-queries.ts index 0bcc2c5c5..9d33b4f52 100644 --- a/src/apps/console/server/gql/queries/app-queries.ts +++ b/src/apps/console/server/gql/queries/app-queries.ts @@ -34,7 +34,7 @@ export const appQueries = (executor: IExecutor) => ({ `, { transformer: (data: ConsoleRestartAppQuery) => data.core_restartApp, - vars: (_: ConsoleRestartAppQueryVariables) => {}, + vars: (_: ConsoleRestartAppQueryVariables) => { }, } ), createApp: executor( @@ -55,7 +55,7 @@ export const appQueries = (executor: IExecutor) => ({ `, { transformer: (data: ConsoleCreateAppMutation) => data.core_createApp, - vars(_: ConsoleCreateAppMutationVariables) {}, + vars(_: ConsoleCreateAppMutationVariables) { }, } ), @@ -79,7 +79,7 @@ export const appQueries = (executor: IExecutor) => ({ transformer: (data: ConsoleUpdateAppMutation) => { return data.core_updateApp; }, - vars(_: ConsoleUpdateAppMutationVariables) {}, + vars(_: ConsoleUpdateAppMutationVariables) { }, } ), interceptApp: executor( @@ -103,7 +103,7 @@ export const appQueries = (executor: IExecutor) => ({ { transformer: (data: ConsoleInterceptAppMutation) => data.core_interceptApp, - vars(_: ConsoleInterceptAppMutationVariables) {}, + vars(_: ConsoleInterceptAppMutationVariables) { }, } ), deleteApp: executor( @@ -122,7 +122,7 @@ export const appQueries = (executor: IExecutor) => ({ `, { transformer: (data: ConsoleDeleteAppMutation) => data.core_deleteApp, - vars(_: ConsoleDeleteAppMutationVariables) {}, + vars(_: ConsoleDeleteAppMutationVariables) { }, } ), getApp: executor( @@ -247,6 +247,11 @@ export const appQueries = (executor: IExecutor) => ({ } } status { + checkList { + description + title + name + } checks isReady lastReadyGeneration @@ -269,7 +274,7 @@ export const appQueries = (executor: IExecutor) => ({ transformer(data: ConsoleGetAppQuery) { return data.core_getApp; }, - vars(_: ConsoleGetAppQueryVariables) {}, + vars(_: ConsoleGetAppQueryVariables) { }, } ), listApps: executor( @@ -390,6 +395,11 @@ export const appQueries = (executor: IExecutor) => ({ name namespace } + checkList { + description + title + name + } } syncStatus { action @@ -414,7 +424,7 @@ export const appQueries = (executor: IExecutor) => ({ `, { transformer: (data: ConsoleListAppsQuery) => data.core_listApps, - vars(_: ConsoleListAppsQueryVariables) {}, + vars(_: ConsoleListAppsQueryVariables) { }, } ), }); diff --git a/src/generated/gql/server.ts b/src/generated/gql/server.ts index 387db054d..2114b22cd 100644 --- a/src/generated/gql/server.ts +++ b/src/generated/gql/server.ts @@ -2506,6 +2506,7 @@ export type ConsoleGetAppQuery = { isReady: boolean; lastReadyGeneration?: number; lastReconcileTime?: any; + checkList?: Array<{ description?: string; title: string; name: string }>; message?: { RawMessage?: any }; resources?: Array<{ apiVersion: string; @@ -2609,6 +2610,11 @@ export type ConsoleListAppsQuery = { name: string; namespace: string; }>; + checkList?: Array<{ + description?: string; + title: string; + name: string; + }>; }; syncStatus: { action: Github__Com___Kloudlite___Api___Pkg___Types__SyncAction; From 37634ba990baef28f4a8ceba27afee025451666d Mon Sep 17 00:00:00 2001 From: Bikash Date: Tue, 12 Mar 2024 13:42:38 +0530 Subject: [PATCH 2/6] Added analytics for console --- lib/app-setup/root.tsx | 36 +++++++++++++++-- lib/utils/tags.client.ts | 55 ++++++++++++++++++++++++++ src/apps/console/root.tsx | 4 +- src/apps/console/utils/gtags.client.ts | 48 ++++++++++++++++++++++ 4 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 lib/utils/tags.client.ts create mode 100644 src/apps/console/utils/gtags.client.ts diff --git a/lib/app-setup/root.tsx b/lib/app-setup/root.tsx index 9ed4b2d9a..a3dd4da08 100644 --- a/lib/app-setup/root.tsx +++ b/lib/app-setup/root.tsx @@ -112,8 +112,8 @@ export function ErrorBoundary() { ? error.stack : JSON.stringify(error.stack, null, 2) : typeof error.stack === 'string' - ? error.stack - : JSON.stringify(error.stack, null, 2)} + ? error.stack + : JSON.stringify(error.stack, null, 2)} ); @@ -160,8 +160,10 @@ const NonIdleProgressBar = () => { const Root = ({ Wrapper = ({ children }: { children: any }) => children, + tagId }: { - Wrapper: (prop: { children: ReactNode }) => JSX.Element; + Wrapper: (prop: { children: ReactNode }) => JSX.Element;, + tagId?: string }) => { const env = useLoaderData(); @@ -172,6 +174,34 @@ const Root = ({ + {/* */} +