diff --git a/dashboard/config/routes.ts b/dashboard/config/routes.ts index 79a6d9433..943ee1709 100644 --- a/dashboard/config/routes.ts +++ b/dashboard/config/routes.ts @@ -3,6 +3,12 @@ path: '/', redirect: `/System`, }, + { + name: 'applications', + icon: 'appstore', + path: `/applications`, + component: './Application', + }, { name: 'system', icon: 'setting', diff --git a/dashboard/src/components/RightContent/WorkSpaceDropDown.tsx b/dashboard/src/components/RightContent/WorkSpaceDropDown.tsx index 3de05f0ea..0d618e152 100644 --- a/dashboard/src/components/RightContent/WorkSpaceDropDown.tsx +++ b/dashboard/src/components/RightContent/WorkSpaceDropDown.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { Menu, message, Spin, Typography } from 'antd'; -import classNames from 'classnames'; import { useModel } from 'umi'; import { DownOutlined } from '@ant-design/icons'; diff --git a/dashboard/src/locales/en-US/menu.ts b/dashboard/src/locales/en-US/menu.ts index 6a1bf0cd6..a639b6288 100644 --- a/dashboard/src/locales/en-US/menu.ts +++ b/dashboard/src/locales/en-US/menu.ts @@ -2,4 +2,5 @@ export default { 'menu.home': 'Home', 'menu.system': 'System', 'menu.system.environment': 'Environment', + 'menu.applications': 'Applications', }; diff --git a/dashboard/src/locales/zh-CN/menu.ts b/dashboard/src/locales/zh-CN/menu.ts index cd8046a6c..334b985ef 100644 --- a/dashboard/src/locales/zh-CN/menu.ts +++ b/dashboard/src/locales/zh-CN/menu.ts @@ -2,4 +2,5 @@ export default { 'menu.home': '首页', 'menu.system': '系统', 'menu.system.environment': '环境', + 'menu.applications': '应用列表', }; diff --git a/dashboard/src/pages/Application/index.tsx b/dashboard/src/pages/Application/index.tsx new file mode 100644 index 000000000..266d3c22e --- /dev/null +++ b/dashboard/src/pages/Application/index.tsx @@ -0,0 +1,125 @@ +import React, { useCallback } from 'react'; + +import { Button, Card, message, Popconfirm, Space, Table } from 'antd'; +import { Link, useModel, useRequest } from 'umi'; + +import { deleteApplication, getApplications } from '@/services/application'; +import compatibleDayjs from '@/utils/compatibleDayjs'; +import { PlusOutlined } from '@ant-design/icons'; +import { PageContainer } from '@ant-design/pro-layout'; + +export default () => { + const { currentEnvironment } = useModel('useEnvironmentModel'); + const { data, loading, run: loadApps } = useRequest( + async () => { + if (currentEnvironment?.envName == null) { + return { code: 200, data: [] } as API.VelaResponse>; + } + return getApplications(currentEnvironment.envName); + }, + { + refreshDeps: [currentEnvironment], + }, + ); + + // delete application + const remove = useCallback( + async (appName: string) => { + const envName = currentEnvironment?.envName; + if (envName == null) { + throw new Error('Unable to determine the current environment name.'); + } + + const response = await deleteApplication(envName, appName); + loadApps(); + return response; + }, + [currentEnvironment], + ); + + return ( + + +
+ + + +
+ record.name} + loading={loading ? { delay: 300 } : undefined} + columns={[ + { + title: 'Name', + dataIndex: 'name', + key: 'name', + render: (text, record) => { + return ( + + {text} + + ); + }, + }, + { + title: 'Status', + dataIndex: 'status', + key: 'status', + render: (text) => { + return text; + }, + }, + { + title: 'Created Time', + dataIndex: 'createdTime', + key: 'createdTime', + render: (text) => { + return compatibleDayjs(text).format('YYYY-MM-DD HH:mm:ss'); + }, + }, + { + title: 'Actions', + dataIndex: 'Actions', + key: 'Actions', + render: (text, { name }) => { + return ( + + { + remove(name).then(({ code, data: content }) => { + if (code === 200) { + message.success({ + content, + key: 'remove', + }); + } else { + message.error({ + content, + key: 'remove', + }); + } + }); + }} + > + + + + ); + }, + }, + ]} + /> + + + ); +}; diff --git a/dashboard/src/pages/System/Environment/index.tsx b/dashboard/src/pages/System/Environment/index.tsx index e1c7384ca..7d24edb81 100644 --- a/dashboard/src/pages/System/Environment/index.tsx +++ b/dashboard/src/pages/System/Environment/index.tsx @@ -98,7 +98,7 @@ export default (): React.ReactNode => { }, }, { - title: 'Operations', + title: 'Actions', dataIndex: 'current', render: (current, { envName, namespace }) => { const active = current === '*'; @@ -113,7 +113,7 @@ export default (): React.ReactNode => { switchCurrenrt({ envName }); }} > - switch + Switch {defaultEnv ? undefined : ( { }} > )} diff --git a/dashboard/src/services/API.d.ts b/dashboard/src/services/API.d.ts index 8bbf12cdf..c10346d2a 100644 --- a/dashboard/src/services/API.d.ts +++ b/dashboard/src/services/API.d.ts @@ -8,7 +8,7 @@ declare namespace API { name: string; status?: string; components?: ComponentMeta[]; - createdTime?: Date; + createdTime?: string; } export interface ComponentMeta { @@ -19,7 +19,7 @@ declare namespace API { traits?: any[]; // ComponentTrait traitsNames?: string[]; app: string; - createdTime?: Date; + createdTime?: string; } interface Environment { @@ -33,4 +33,10 @@ declare namespace API { interface EnvironmentBody { namespace: string; } + + interface Application { + name: string; + status: string; + createdTime: string; + } } diff --git a/dashboard/src/services/application.ts b/dashboard/src/services/application.ts new file mode 100644 index 000000000..97bedd3a3 --- /dev/null +++ b/dashboard/src/services/application.ts @@ -0,0 +1,22 @@ +import { request } from 'umi'; + +const BASE_PATH = '/api/envs'; + +/* + * application list: get /api/envs/{env}/apps + */ +export async function getApplications( + envName: string, +): Promise> { + return request(`${BASE_PATH}/${envName}/apps`); +} + +/* + * delete application: delete /api/envs/{env}/apps/{app} + */ +export async function deleteApplication( + envName: string, + appName: string, +): Promise> { + return request(`${BASE_PATH}/${envName}/apps/${appName}`, { method: 'delete' }); +} diff --git a/dashboard/src/utils/compatibleDayjs.ts b/dashboard/src/utils/compatibleDayjs.ts new file mode 100644 index 000000000..7d858c10d --- /dev/null +++ b/dashboard/src/utils/compatibleDayjs.ts @@ -0,0 +1,14 @@ +import dayjs from 'dayjs'; +import customParseFormat from 'dayjs/plugin/customParseFormat'; +import timezone from 'dayjs/plugin/timezone'; +import utc from 'dayjs/plugin/utc'; + +dayjs.extend(customParseFormat); +dayjs.extend(utc); +dayjs.extend(timezone); + +const FORMAT = 'YYYY-MM-DD HH:mm:ss ZZ'; + +export default function compatibleDayjs(date: string) { + return dayjs(date, FORMAT).tz(); +} diff --git a/pkg/serverlib/application.go b/pkg/serverlib/application.go index b7dcbfd93..90b68d721 100644 --- a/pkg/serverlib/application.go +++ b/pkg/serverlib/application.go @@ -76,6 +76,10 @@ func ListApplications(ctx context.Context, c client.Client, opt Option) ([]apis. } for _, a := range appConfigList.Items { + // ignore the deleted resource + if a.GetDeletionGracePeriodSeconds() != nil { + continue + } applicationMeta, err := RetrieveApplicationStatusByName(ctx, c, a.Name, a.Namespace) if err != nil { return applicationMetaList, nil