From b700863d56753621e1629f0b42aa1ab235380fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=AF=E8=88=AA?= <12344192+zhangsetsail@user.noreply.gitee.com> Date: Mon, 16 Mar 2026 15:10:42 +0800 Subject: [PATCH 001/147] feat: add platform resource API service and DVA model --- src/models/platformResources.js | 97 +++++++++++++++++++++++++++++++ src/services/platformResource.js | 98 ++++++++++++++++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 src/models/platformResources.js create mode 100644 src/services/platformResource.js diff --git a/src/models/platformResources.js b/src/models/platformResources.js new file mode 100644 index 000000000..f06770010 --- /dev/null +++ b/src/models/platformResources.js @@ -0,0 +1,97 @@ +import { + getPlatformResourcesOverview, + getPlatformStorageClasses, + getPlatformPersistentVolumes, + getPlatformResourceTypes, + getPlatformResourceMetrics, + getPlatformResourceAlerts, +} from '../services/platformResources'; + +export default { + namespace: 'platformResources', + + state: { + overview: {}, + storageClasses: [], + persistentVolumes: [], + resourceTypes: [], + metrics: {}, + alerts: [], + }, + + effects: { + *fetchOverview({ payload, callback, handleError }, { call, put }) { + const response = yield call(getPlatformResourcesOverview, payload, handleError); + if (response) { + yield put({ type: 'saveOverview', payload: response }); + if (callback) callback(response); + } + }, + + *fetchStorageClasses({ payload, callback, handleError }, { call, put }) { + const response = yield call(getPlatformStorageClasses, payload, handleError); + if (response) { + yield put({ type: 'saveStorageClasses', payload: response }); + if (callback) callback(response); + } + }, + + *fetchPersistentVolumes({ payload, callback, handleError }, { call, put }) { + const response = yield call(getPlatformPersistentVolumes, payload, handleError); + if (response) { + yield put({ type: 'savePersistentVolumes', payload: response }); + if (callback) callback(response); + } + }, + + *fetchResourceTypes({ payload, callback, handleError }, { call, put }) { + const response = yield call(getPlatformResourceTypes, payload, handleError); + if (response) { + yield put({ type: 'saveResourceTypes', payload: response }); + if (callback) callback(response); + } + }, + + *fetchMetrics({ payload, callback, handleError }, { call, put }) { + const response = yield call(getPlatformResourceMetrics, payload, handleError); + if (response) { + yield put({ type: 'saveMetrics', payload: response }); + if (callback) callback(response); + } + }, + + *fetchAlerts({ payload, callback, handleError }, { call, put }) { + const response = yield call(getPlatformResourceAlerts, payload, handleError); + if (response) { + yield put({ type: 'saveAlerts', payload: response }); + if (callback) callback(response); + } + }, + }, + + reducers: { + saveOverview(state, action) { + return { ...state, overview: action.payload }; + }, + + saveStorageClasses(state, action) { + return { ...state, storageClasses: action.payload }; + }, + + savePersistentVolumes(state, action) { + return { ...state, persistentVolumes: action.payload }; + }, + + saveResourceTypes(state, action) { + return { ...state, resourceTypes: action.payload }; + }, + + saveMetrics(state, action) { + return { ...state, metrics: action.payload }; + }, + + saveAlerts(state, action) { + return { ...state, alerts: action.payload }; + }, + }, +}; diff --git a/src/services/platformResource.js b/src/services/platformResource.js new file mode 100644 index 000000000..ac9158910 --- /dev/null +++ b/src/services/platformResource.js @@ -0,0 +1,98 @@ +import apiconfig from '../../config/api.config'; +import request from '../utils/request'; + +/** + * Get storage overview information + */ +export async function getStorageOverview(body = {}) { + return request( + `${apiconfig.baseUrl}/console/platform/storage/overview`, + { + method: 'get', + params: { + cluster_id: body.cluster_id + } + } + ); +} + +/** + * Get available storage classes + */ +export async function getStorageClasses(body = {}) { + return request( + `${apiconfig.baseUrl}/console/platform/storage/classes`, + { + method: 'get', + params: { + cluster_id: body.cluster_id + } + } + ); +} + +/** + * Get persistent volumes + */ +export async function getPersistentVolumes(body = {}) { + return request( + `${apiconfig.baseUrl}/console/platform/storage/volumes`, + { + method: 'get', + params: { + cluster_id: body.cluster_id, + page: body.page, + page_size: body.page_size + } + } + ); +} + +/** + * Get cluster resource types + */ +export async function getClusterResourceTypes(body = {}) { + return request( + `${apiconfig.baseUrl}/console/platform/cluster/resource-types`, + { + method: 'get', + params: { + cluster_id: body.cluster_id + } + } + ); +} + +/** + * Get cluster resources + */ +export async function getClusterResources(body = {}) { + return request( + `${apiconfig.baseUrl}/console/platform/cluster/resources`, + { + method: 'get', + params: { + cluster_id: body.cluster_id, + resource_type: body.resource_type, + page: body.page, + page_size: body.page_size + } + } + ); +} + +/** + * Get cluster resource detail + */ +export async function getClusterResourceDetail(body = {}) { + return request( + `${apiconfig.baseUrl}/console/platform/cluster/resources/${body.resource_id}`, + { + method: 'get', + params: { + cluster_id: body.cluster_id, + resource_type: body.resource_type + } + } + ); +} From 68fb0cb4609a45c53a1fccd63e7ae3a8f63afc18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=AF=E8=88=AA?= <12344192+zhangsetsail@user.noreply.gitee.com> Date: Mon, 16 Mar 2026 15:10:42 +0800 Subject: [PATCH 002/147] feat: add storage page component --- src/pages/PlatformResources/Storage/index.js | 337 ++++++++++++++++++ .../PlatformResources/Storage/index.less | 100 ++++++ 2 files changed, 437 insertions(+) create mode 100644 src/pages/PlatformResources/Storage/index.js create mode 100644 src/pages/PlatformResources/Storage/index.less diff --git a/src/pages/PlatformResources/Storage/index.js b/src/pages/PlatformResources/Storage/index.js new file mode 100644 index 000000000..0356ebdee --- /dev/null +++ b/src/pages/PlatformResources/Storage/index.js @@ -0,0 +1,337 @@ +import React, { PureComponent } from 'react'; +import { Card, Row, Col, Table, Spin, Button, Modal, notification } from 'antd'; +import { connect } from 'dva'; +import { formatMessage } from '@/utils/intl'; +import globalUtil from '@/utils/global'; +import styles from './index.less'; + +@connect(({ loading, platformResources }) => ({ + storageLoading: loading.effects['platformResources/fetchStorageData'], + storageClassList: platformResources.storageClassList || [], + pvList: platformResources.pvList || [], + storageStats: platformResources.storageStats || {} +})) +export default class StoragePage extends PureComponent { + constructor(props) { + super(props); + this.state = { + selectedStorageClass: null, + selectedPV: null + }; + } + + componentDidMount() { + this.loadStorageData(); + } + + loadStorageData = () => { + const { dispatch } = this.props; + const regionName = globalUtil.getCurrRegionName(); + + dispatch({ + type: 'platformResources/fetchStorageData', + payload: { + region_name: regionName + }, + callback: res => { + if (res && res.status_code !== 200) { + notification.error({ + message: formatMessage({ id: 'notification.error.load_storage' }) + }); + } + } + }); + }; + + handleRefresh = () => { + this.loadStorageData(); + }; + + handleDeleteStorageClass = (record) => { + const { dispatch } = this.props; + const regionName = globalUtil.getCurrRegionName(); + + Modal.confirm({ + title: formatMessage({ id: 'modal.confirm.delete' }), + content: formatMessage({ id: 'modal.confirm.delete_storage_class' }), + okText: formatMessage({ id: 'button.confirm' }), + cancelText: formatMessage({ id: 'button.cancel' }), + onOk: () => { + dispatch({ + type: 'platformResources/deleteStorageClass', + payload: { + region_name: regionName, + name: record.name + }, + callback: res => { + if (res && res.status_code === 200) { + notification.success({ + message: formatMessage({ id: 'notification.success.delete' }) + }); + this.loadStorageData(); + } + } + }); + } + }); + }; + + handleDeletePV = (record) => { + const { dispatch } = this.props; + const regionName = globalUtil.getCurrRegionName(); + + Modal.confirm({ + title: formatMessage({ id: 'modal.confirm.delete' }), + content: formatMessage({ id: 'modal.confirm.delete_pv' }), + okText: formatMessage({ id: 'button.confirm' }), + cancelText: formatMessage({ id: 'button.cancel' }), + onOk: () => { + dispatch({ + type: 'platformResources/deletePV', + payload: { + region_name: regionName, + name: record.name + }, + callback: res => { + if (res && res.status_code === 200) { + notification.success({ + message: formatMessage({ id: 'notification.success.delete' }) + }); + this.loadStorageData(); + } + } + }); + } + }); + }; + + renderStorageStats = () => { + const { storageStats } = this.props; + + const stats = [ + { + label: formatMessage({ id: 'storage.stats.total_capacity' }), + value: storageStats.totalCapacity || '0 Gi', + color: '#155aef' + }, + { + label: formatMessage({ id: 'storage.stats.used_capacity' }), + value: storageStats.usedCapacity || '0 Gi', + color: '#18B633' + }, + { + label: formatMessage({ id: 'storage.stats.available_capacity' }), + value: storageStats.availableCapacity || '0 Gi', + color: '#FF8D3C' + }, + { + label: formatMessage({ id: 'storage.stats.storage_class_count' }), + value: storageStats.storageClassCount || 0, + color: '#FC481B' + } + ]; + + return ( + + {stats.map((stat, index) => ( + + +
{stat.label}
+
+ {stat.value} +
+
+ + ))} +
+ ); + }; + + renderStorageClassTable = () => { + const { storageClassList, storageLoading } = this.props; + + const columns = [ + { + title: formatMessage({ id: 'storage.table.name' }), + dataIndex: 'name', + key: 'name', + width: '20%' + }, + { + title: formatMessage({ id: 'storage.table.provisioner' }), + dataIndex: 'provisioner', + key: 'provisioner', + width: '20%' + }, + { + title: formatMessage({ id: 'storage.table.reclaim_policy' }), + dataIndex: 'reclaimPolicy', + key: 'reclaimPolicy', + width: '15%' + }, + { + title: formatMessage({ id: 'storage.table.allow_volume_expansion' }), + dataIndex: 'allowVolumeExpansion', + key: 'allowVolumeExpansion', + width: '15%', + render: (text) => (text ? formatMessage({ id: 'common.yes' }) : formatMessage({ id: 'common.no' })) + }, + { + title: formatMessage({ id: 'storage.table.pv_count' }), + dataIndex: 'pvCount', + key: 'pvCount', + width: '10%' + }, + { + title: formatMessage({ id: 'common.action' }), + key: 'action', + width: '20%', + render: (text, record) => ( +
+ +
+ ) + } + ]; + + return ( + {formatMessage({ id: 'button.refresh' })}} + className={styles.tableCard} + > + + + ); + }; + + renderPVTable = () => { + const { pvList, storageLoading } = this.props; + + const columns = [ + { + title: formatMessage({ id: 'storage.table.name' }), + dataIndex: 'name', + key: 'name', + width: '15%' + }, + { + title: formatMessage({ id: 'storage.table.storage_class' }), + dataIndex: 'storageClassName', + key: 'storageClassName', + width: '15%' + }, + { + title: formatMessage({ id: 'storage.table.capacity' }), + dataIndex: 'capacity', + key: 'capacity', + width: '12%' + }, + { + title: formatMessage({ id: 'storage.table.access_modes' }), + dataIndex: 'accessModes', + key: 'accessModes', + width: '15%', + render: (modes) => (modes ? modes.join(', ') : '-') + }, + { + title: formatMessage({ id: 'storage.table.status' }), + dataIndex: 'status', + key: 'status', + width: '12%', + render: (status) => { + let color = '#676f83'; + if (status === 'Bound') { + color = '#18B633'; + } else if (status === 'Available') { + color = '#155aef'; + } else if (status === 'Released') { + color = '#FF8D3C'; + } else if (status === 'Failed') { + color = '#FC481B'; + } + return {status}; + } + }, + { + title: formatMessage({ id: 'storage.table.claim' }), + dataIndex: 'claimRef', + key: 'claimRef', + width: '15%', + render: (claim) => (claim ? `${claim.namespace}/${claim.name}` : '-') + }, + { + title: formatMessage({ id: 'common.action' }), + key: 'action', + width: '16%', + render: (text, record) => ( +
+ +
+ ) + } + ]; + + return ( + {formatMessage({ id: 'button.refresh' })}} + className={styles.tableCard} + > +
+ + ); + }; + + render() { + const { storageLoading } = this.props; + + return ( + +
+
+

{formatMessage({ id: 'storage.page.title' })}

+
+ + {this.renderStorageStats()} + + +
+ {this.renderStorageClassTable()} + + + + + + {this.renderPVTable()} + + + + + ); + } +} diff --git a/src/pages/PlatformResources/Storage/index.less b/src/pages/PlatformResources/Storage/index.less new file mode 100644 index 000000000..dc0fb3ab9 --- /dev/null +++ b/src/pages/PlatformResources/Storage/index.less @@ -0,0 +1,100 @@ +@import '~antd/lib/style/themes/default.less'; + +.storageContainer { + padding: 24px; + background-color: @rbd-background-color; + min-height: 100vh; +} + +.header { + margin-bottom: 24px; + + h2 { + font-size: @rbd-title-size; + color: @heading-color; + margin: 0; + font-weight: 500; + } +} + +.statsRow { + margin-bottom: 24px; +} + +.statCard { + border-radius: 4px; + box-shadow: @rbd-min-card-shadow; + transition: all 0.3s ease; + + &:hover { + box-shadow: @rbd-card-shadow-hover; + } +} + +.statLabel { + font-size: @rbd-auxiliary-size; + color: @text-color-secondary; + margin-bottom: 12px; + font-weight: 400; +} + +.statValue { + font-size: @rbd-title-big-size; + font-weight: 600; + line-height: 1.2; +} + +.tableCard { + border-radius: 4px; + box-shadow: @rbd-min-card-shadow; + margin-bottom: 24px; + + :global { + .ant-card-head { + border-bottom: 1px solid @border-color-base; + padding: 16px 24px; + } + + .ant-card-body { + padding: 0; + } + + .ant-table { + font-size: @rbd-content-size; + } + + .ant-table-thead > tr > th { + background-color: #fafafa; + color: @heading-color; + font-weight: 600; + border-bottom: 1px solid @border-color-base; + } + + .ant-table-tbody > tr { + &:hover > td { + background-color: #f5f5f5; + } + } + + .ant-table-tbody > tr > td { + border-bottom: 1px solid @border-color-base; + padding: 12px 16px; + } + + .ant-btn-link { + color: @primary-color; + padding: 0; + height: auto; + + &:hover { + color: darken(@primary-color, 10%); + } + } + + .ant-pagination { + margin: 16px 0; + text-align: right; + padding-right: 16px; + } + } +} From 2d82939d63ff46f991ca8195b97393dfb243d6c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=AF=E8=88=AA?= <12344192+zhangsetsail@user.noreply.gitee.com> Date: Mon, 16 Mar 2026 15:10:43 +0800 Subject: [PATCH 003/147] feat: add cluster resources page and routes --- config/router.config.js | 16 +- .../ClusterResources/index.js | 324 ++++++++++++++++++ .../ClusterResources/index.less | 97 ++++++ 3 files changed, 435 insertions(+), 2 deletions(-) create mode 100644 src/pages/PlatformResources/ClusterResources/index.js create mode 100644 src/pages/PlatformResources/ClusterResources/index.less diff --git a/config/router.config.js b/config/router.config.js index e0751d146..6edd965ff 100644 --- a/config/router.config.js +++ b/config/router.config.js @@ -282,6 +282,18 @@ export default [ name: 'ClusterLink', authority: ['admin', 'user'] }, + { + path: '/enterprise/:eid/region/:region_name/platform-resources/storage', + component: './PlatformResources/Storage', + name: 'PlatformResourcesStorage', + authority: ['admin', 'user'] + }, + { + path: '/enterprise/:eid/region/:region_name/platform-resources/cluster-resources', + component: './PlatformResources/ClusterResources', + name: 'PlatformResourcesClusterResources', + authority: ['admin', 'user'] + }, { component: '404' } ] }, @@ -475,7 +487,7 @@ export default [ name: 'code', authority: ['admin', 'user'] }, - + // yaml创建 { path: @@ -561,7 +573,7 @@ export default [ name: 'check', authority: ['admin', 'user'] }, - + { path: '/team/:teamName/region/:regionName/create/create-compose-check/:appID/:composeId', diff --git a/src/pages/PlatformResources/ClusterResources/index.js b/src/pages/PlatformResources/ClusterResources/index.js new file mode 100644 index 000000000..dc4b43bcc --- /dev/null +++ b/src/pages/PlatformResources/ClusterResources/index.js @@ -0,0 +1,324 @@ +/* eslint-disable react/sort-comp */ +import React, { PureComponent } from 'react'; +import { connect } from 'dva'; +import { Row, Col, Tree, Table, Button, Drawer, Form, Input, notification, Spin, Empty } from 'antd'; +import { formatMessage } from '@/utils/intl'; +import PageHeaderLayout from '@/layouts/PageHeaderLayout'; +import globalUtil from '../../../utils/global'; +import roleUtil from '../../../utils/newRole'; +import styles from './index.less'; + +const { TreeNode } = Tree; + +@connect(({ teamControl, platformResources }) => ({ + currentTeamPermissionsInfo: teamControl.currentTeamPermissionsInfo, + resourceList: platformResources.resourceList, + resourceTypes: platformResources.resourceTypes, + loading: platformResources.loading, +})) +@Form.create() +class ClusterResources extends PureComponent { + constructor(props) { + super(props); + this.state = { + selectedResourceType: null, + expandedKeys: [], + selectedKeys: [], + drawerVisible: false, + drawerTitle: formatMessage({ id: 'platformResources.cluster.add' }), + drawerType: 'add', + formData: {}, + resourcePermission: roleUtil.queryPermissionsInfo( + this.props.currentTeamPermissionsInfo && this.props.currentTeamPermissionsInfo.team, + 'platform_resources', + 'cluster' + ), + }; + } + + componentDidMount() { + this.fetchResourceTypes(); + } + + fetchResourceTypes = () => { + const { dispatch } = this.props; + dispatch({ + type: 'platformResources/fetchResourceTypes', + payload: { + team_name: globalUtil.getCurrTeamName(), + resource_category: 'cluster', + }, + }); + }; + + fetchResourceList = (resourceType) => { + const { dispatch } = this.props; + dispatch({ + type: 'platformResources/fetchResourceList', + payload: { + team_name: globalUtil.getCurrTeamName(), + resource_type: resourceType, + }, + }); + }; + + handleTreeSelect = (selectedKeys, event) => { + if (selectedKeys.length > 0) { + const resourceType = selectedKeys[0]; + this.setState({ selectedResourceType: resourceType, selectedKeys }); + this.fetchResourceList(resourceType); + } + }; + + handleTreeExpand = (expandedKeys) => { + this.setState({ expandedKeys }); + }; + + handleAddResource = () => { + this.setState({ + drawerVisible: true, + drawerType: 'add', + drawerTitle: formatMessage({ id: 'platformResources.cluster.add' }), + formData: {}, + }); + }; + + handleEditResource = (record) => { + this.setState({ + drawerVisible: true, + drawerType: 'edit', + drawerTitle: formatMessage({ id: 'platformResources.cluster.edit' }), + formData: record, + }); + }; + + handleDeleteResource = (record) => { + const { dispatch } = this.props; + const { selectedResourceType } = this.state; + dispatch({ + type: 'platformResources/deleteResource', + payload: { + team_name: globalUtil.getCurrTeamName(), + resource_id: record.id, + }, + callback: () => { + notification.success({ + message: formatMessage({ id: 'platformResources.cluster.deleteSuccess' }), + }); + this.fetchResourceList(selectedResourceType); + }, + }); + }; + + handleDrawerClose = () => { + this.setState({ drawerVisible: false }); + }; + + handleDrawerSubmit = () => { + const { form, dispatch } = this.props; + const { drawerType, selectedResourceType, formData } = this.state; + + form.validateFields((err, values) => { + if (!err) { + const payload = { + team_name: globalUtil.getCurrTeamName(), + resource_type: selectedResourceType, + ...values, + }; + + if (drawerType === 'edit') { + payload.resource_id = formData.id; + } + + const actionType = drawerType === 'add' + ? 'platformResources/createResource' + : 'platformResources/updateResource'; + + dispatch({ + type: actionType, + payload, + callback: () => { + notification.success({ + message: formatMessage({ + id: drawerType === 'add' + ? 'platformResources.cluster.addSuccess' + : 'platformResources.cluster.editSuccess', + }), + }); + this.handleDrawerClose(); + this.fetchResourceList(selectedResourceType); + }, + }); + } + }); + }; + + renderResourceTree = () => { + const { resourceTypes } = this.props; + const { expandedKeys } = this.state; + + if (!resourceTypes || resourceTypes.length === 0) { + return ; + } + + return ( + + {resourceTypes.map((type) => ( + + ))} + + ); + }; + + renderResourceTable = () => { + const { resourceList, loading } = this.props; + const { resourcePermission } = this.state; + + const columns = [ + { + title: formatMessage({ id: 'platformResources.cluster.name' }), + dataIndex: 'name', + key: 'name', + }, + { + title: formatMessage({ id: 'platformResources.cluster.type' }), + dataIndex: 'type', + key: 'type', + }, + { + title: formatMessage({ id: 'platformResources.cluster.status' }), + dataIndex: 'status', + key: 'status', + render: (status) => { + const statusMap = { + active: formatMessage({ id: 'platformResources.cluster.statusActive' }), + inactive: formatMessage({ id: 'platformResources.cluster.statusInactive' }), + }; + return statusMap[status] || status; + }, + }, + { + title: formatMessage({ id: 'platformResources.cluster.action' }), + key: 'action', + render: (text, record) => ( + + {resourcePermission && resourcePermission.edit && ( + <> + this.handleEditResource(record)}> + {formatMessage({ id: 'platformResources.cluster.edit' })} + + | + + )} + {resourcePermission && resourcePermission.delete && ( + this.handleDeleteResource(record)}> + {formatMessage({ id: 'platformResources.cluster.delete' })} + + )} + + ), + }, + ]; + + return ( + +
+ + ); + }; + + renderDrawer = () => { + const { form } = this.props; + const { drawerVisible, drawerTitle, drawerType, formData } = this.state; + const { getFieldDecorator } = form; + + return ( + +
+ + {getFieldDecorator('name', { + initialValue: formData.name || '', + rules: [ + { + required: true, + message: formatMessage({ id: 'platformResources.cluster.nameRequired' }), + }, + ], + })()} + + + + {getFieldDecorator('description', { + initialValue: formData.description || '', + })()} + + + + + + + +
+ ); + }; + + render() { + const { resourcePermission } = this.state; + + return ( + +
+ +
+
+

{formatMessage({ id: 'platformResources.cluster.resourceTypes' })}

+
+
+ {this.renderResourceTree()} +
+ + +
+

{formatMessage({ id: 'platformResources.cluster.resourceList' })}

+ {resourcePermission && resourcePermission.add && ( + + )} +
+
+ {this.renderResourceTable()} +
+ + + {this.renderDrawer()} + + + ); + } +} + +export default ClusterResources; diff --git a/src/pages/PlatformResources/ClusterResources/index.less b/src/pages/PlatformResources/ClusterResources/index.less new file mode 100644 index 000000000..f39bcc18d --- /dev/null +++ b/src/pages/PlatformResources/ClusterResources/index.less @@ -0,0 +1,97 @@ +@import '~antd/lib/style/themes/default.less'; + +.container { + padding: 20px; + background-color: @rbd-background-color; + min-height: 100vh; +} + +.treePanel { + background-color: #fff; + border-radius: 4px; + box-shadow: @rbd-min-card-shadow; + padding: 16px; +} + +.treeHeader { + margin-bottom: 16px; + border-bottom: 1px solid @border-color-base; + padding-bottom: 12px; + + h3 { + margin: 0; + font-size: @rbd-sub-title-size; + color: @heading-color; + font-weight: 500; + } +} + +.treeContent { + max-height: 600px; + overflow-y: auto; + + :global { + .ant-tree { + background-color: transparent; + } + + .ant-tree-node-content-wrapper { + color: @text-color; + + &:hover { + background-color: #f5f5f5; + } + } + + .ant-tree-node-selected .ant-tree-node-content-wrapper { + background-color: @primary-color; + color: #fff; + } + } +} + +.listPanel { + background-color: #fff; + border-radius: 4px; + box-shadow: @rbd-min-card-shadow; + padding: 16px; +} + +.listHeader { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + border-bottom: 1px solid @border-color-base; + padding-bottom: 12px; + + h3 { + margin: 0; + font-size: @rbd-sub-title-size; + color: @heading-color; + font-weight: 500; + } +} + +.listContent { + :global { + .ant-table { + background-color: transparent; + } + + .ant-table-thead > tr > th { + background-color: #fafafa; + color: @heading-color; + font-weight: 500; + } + + .ant-table-tbody > tr:hover > td { + background-color: #f5f5f5; + } + } +} + +.divider { + margin: 0 8px; + color: @border-color-base; +} From 702c49008dd2f5b6d920a0af964df0ddebb7d047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=AF=E8=88=AA?= <12344192+zhangsetsail@user.noreply.gitee.com> Date: Mon, 16 Mar 2026 16:06:04 +0800 Subject: [PATCH 004/147] fix: correct service import path in platformResources model --- src/models/platformResources.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/platformResources.js b/src/models/platformResources.js index f06770010..8309ea7ca 100644 --- a/src/models/platformResources.js +++ b/src/models/platformResources.js @@ -5,7 +5,7 @@ import { getPlatformResourceTypes, getPlatformResourceMetrics, getPlatformResourceAlerts, -} from '../services/platformResources'; +} from '../services/platformResource'; export default { namespace: 'platformResources', From 042d3f6a76c147643f8d96a7df941146a000a56a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=AF=E8=88=AA?= <12344192+zhangsetsail@user.noreply.gitee.com> Date: Mon, 16 Mar 2026 17:23:36 +0800 Subject: [PATCH 005/147] feat: add platform resources menu entry to enterprise and team sidebar --- config/router.config.js | 12 ++++++++++++ src/common/enterpriseMenu.js | 11 +++++++++++ src/common/getMenuSvg.js | 5 +++++ src/common/teamMenu.js | 8 ++++++++ src/locales/en-US/menu.js | 2 ++ src/locales/zh-CN/menu.js | 2 ++ 6 files changed, 40 insertions(+) diff --git a/config/router.config.js b/config/router.config.js index 6edd965ff..588f25dca 100644 --- a/config/router.config.js +++ b/config/router.config.js @@ -352,6 +352,18 @@ export default [ authority: ['admin', 'user'], title: '流水线' }, + { + path: '/team/:teamName/region/:regionName/platform-resources/storage', + component: './PlatformResources/Storage', + name: 'teamPlatformResourcesStorage', + authority: ['admin', 'user'] + }, + { + path: '/team/:teamName/region/:regionName/platform-resources/cluster-resources', + component: './PlatformResources/ClusterResources', + name: 'teamPlatformResourcesClusterResources', + authority: ['admin', 'user'] + }, { path: '/team/:teamName/region/:regionName/apps/:appID/upgrade', component: './Upgrade', diff --git a/src/common/enterpriseMenu.js b/src/common/enterpriseMenu.js index d327a94b3..27e643b64 100644 --- a/src/common/enterpriseMenu.js +++ b/src/common/enterpriseMenu.js @@ -67,6 +67,17 @@ function menuData(eid, currentUser, enterprise, pluginList, clusterList) { } ]; + // 平台资源(有集群时才显示) + if (clusterList && clusterList.length > 0) { + const firstRegion = clusterList[0].region_name; + resourceItems.push({ + name: formatMessage({ id: 'menu.enterprise.platformResources', defaultMessage: '平台资源' }), + icon: getMenuSvg.getSvg('resource'), + path: `/enterprise/${eid}/region/${firstRegion}/platform-resources/storage`, + authority: ['admin', 'user'] + }); + } + // 计量计费 const billPlugin = PluginUtil.getPluginInfo(pluginList, 'rainbond-bill'); if (billPlugin && Object.keys(billPlugin).length !== 0) { diff --git a/src/common/getMenuSvg.js b/src/common/getMenuSvg.js index ea2062b18..4ca01226b 100644 --- a/src/common/getMenuSvg.js +++ b/src/common/getMenuSvg.js @@ -153,6 +153,11 @@ const getMenuSvg = { ), + resource: ( + + + + ), monitoringSvg: ( diff --git a/src/common/teamMenu.js b/src/common/teamMenu.js index 0bac8aa68..c538c4c74 100644 --- a/src/common/teamMenu.js +++ b/src/common/teamMenu.js @@ -92,6 +92,14 @@ function menuData(teamName, regionName, permissionsInfo, pluginList) { }); } + // 资源视图 + adminItems.push({ + name: formatMessage({ id: 'menu.team.platformResources', defaultMessage: '资源视图' }), + icon: getMenuSvg.getSvg('resource'), + path: `team/${teamName}/region/${regionName}/platform-resources/storage`, + authority: ['admin', 'user'] + }); + if (adminItems.length > 0) { menuGroups.push({ groupKey: 'administration', diff --git a/src/locales/en-US/menu.js b/src/locales/en-US/menu.js index 246e4028e..7cc893146 100644 --- a/src/locales/en-US/menu.js +++ b/src/locales/en-US/menu.js @@ -13,6 +13,7 @@ const enterpriseMenu = { 'menu.enterprise.observability': 'Observability', 'menu.enterprise.billing': 'Billing', 'menu.enterprise.plugins': 'Plugins', + 'menu.enterprise.platformResources': 'Platform Resources', // Group titles 'menu.group.basic': 'Basic', 'menu.group.observability': 'Observability', @@ -44,6 +45,7 @@ const teamMenu = { 'menu.team.plugin': 'Plugin', 'menu.team.setting': 'Manage', 'menu.team.pipeline': 'Pipeline', + 'menu.team.platformResources': 'Resources', } const appMenu = { diff --git a/src/locales/zh-CN/menu.js b/src/locales/zh-CN/menu.js index c79335424..7f0c872d6 100644 --- a/src/locales/zh-CN/menu.js +++ b/src/locales/zh-CN/menu.js @@ -14,6 +14,7 @@ const enterpriseMenu = { 'menu.enterprise.observability': '可观测性', 'menu.enterprise.billing': '计量计费', 'menu.enterprise.plugins': '插件列表', + 'menu.enterprise.platformResources': '平台资源', // 分组标题 'menu.group.basic': '基础功能', 'menu.group.observability': '可观测性', @@ -44,6 +45,7 @@ const teamMenu = { 'menu.team.setting': '团队管理', 'menu.team.pipeline': '流水线', 'menu.team.log': '日志查询', + 'menu.team.platformResources': '资源视图', 'menu.team.link': '链路追踪', 'menu.team.create.wizard': '向导页', } From d43af862a861193db0cc1ce39c6c9eb2ad072404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=AF=E8=88=AA?= <12344192+zhangsetsail@user.noreply.gitee.com> Date: Mon, 16 Mar 2026 17:41:56 +0800 Subject: [PATCH 006/147] Revert "feat: add platform resource API service and DVA model" This reverts commit b700863d56753621e1629f0b42aa1ab235380fe0. --- config/router.config.js | 28 +- src/common/enterpriseMenu.js | 11 - src/common/getMenuSvg.js | 5 - src/common/teamMenu.js | 8 - src/locales/en-US/menu.js | 2 - src/locales/zh-CN/menu.js | 2 - src/models/platformResources.js | 97 ----- .../ClusterResources/index.js | 324 ----------------- .../ClusterResources/index.less | 97 ----- src/pages/PlatformResources/Storage/index.js | 337 ------------------ .../PlatformResources/Storage/index.less | 100 ------ src/services/platformResource.js | 98 ----- 12 files changed, 2 insertions(+), 1107 deletions(-) delete mode 100644 src/models/platformResources.js delete mode 100644 src/pages/PlatformResources/ClusterResources/index.js delete mode 100644 src/pages/PlatformResources/ClusterResources/index.less delete mode 100644 src/pages/PlatformResources/Storage/index.js delete mode 100644 src/pages/PlatformResources/Storage/index.less delete mode 100644 src/services/platformResource.js diff --git a/config/router.config.js b/config/router.config.js index 588f25dca..e0751d146 100644 --- a/config/router.config.js +++ b/config/router.config.js @@ -282,18 +282,6 @@ export default [ name: 'ClusterLink', authority: ['admin', 'user'] }, - { - path: '/enterprise/:eid/region/:region_name/platform-resources/storage', - component: './PlatformResources/Storage', - name: 'PlatformResourcesStorage', - authority: ['admin', 'user'] - }, - { - path: '/enterprise/:eid/region/:region_name/platform-resources/cluster-resources', - component: './PlatformResources/ClusterResources', - name: 'PlatformResourcesClusterResources', - authority: ['admin', 'user'] - }, { component: '404' } ] }, @@ -352,18 +340,6 @@ export default [ authority: ['admin', 'user'], title: '流水线' }, - { - path: '/team/:teamName/region/:regionName/platform-resources/storage', - component: './PlatformResources/Storage', - name: 'teamPlatformResourcesStorage', - authority: ['admin', 'user'] - }, - { - path: '/team/:teamName/region/:regionName/platform-resources/cluster-resources', - component: './PlatformResources/ClusterResources', - name: 'teamPlatformResourcesClusterResources', - authority: ['admin', 'user'] - }, { path: '/team/:teamName/region/:regionName/apps/:appID/upgrade', component: './Upgrade', @@ -499,7 +475,7 @@ export default [ name: 'code', authority: ['admin', 'user'] }, - + // yaml创建 { path: @@ -585,7 +561,7 @@ export default [ name: 'check', authority: ['admin', 'user'] }, - + { path: '/team/:teamName/region/:regionName/create/create-compose-check/:appID/:composeId', diff --git a/src/common/enterpriseMenu.js b/src/common/enterpriseMenu.js index 27e643b64..d327a94b3 100644 --- a/src/common/enterpriseMenu.js +++ b/src/common/enterpriseMenu.js @@ -67,17 +67,6 @@ function menuData(eid, currentUser, enterprise, pluginList, clusterList) { } ]; - // 平台资源(有集群时才显示) - if (clusterList && clusterList.length > 0) { - const firstRegion = clusterList[0].region_name; - resourceItems.push({ - name: formatMessage({ id: 'menu.enterprise.platformResources', defaultMessage: '平台资源' }), - icon: getMenuSvg.getSvg('resource'), - path: `/enterprise/${eid}/region/${firstRegion}/platform-resources/storage`, - authority: ['admin', 'user'] - }); - } - // 计量计费 const billPlugin = PluginUtil.getPluginInfo(pluginList, 'rainbond-bill'); if (billPlugin && Object.keys(billPlugin).length !== 0) { diff --git a/src/common/getMenuSvg.js b/src/common/getMenuSvg.js index 4ca01226b..ea2062b18 100644 --- a/src/common/getMenuSvg.js +++ b/src/common/getMenuSvg.js @@ -153,11 +153,6 @@ const getMenuSvg = { ), - resource: ( - - - - ), monitoringSvg: ( diff --git a/src/common/teamMenu.js b/src/common/teamMenu.js index c538c4c74..0bac8aa68 100644 --- a/src/common/teamMenu.js +++ b/src/common/teamMenu.js @@ -92,14 +92,6 @@ function menuData(teamName, regionName, permissionsInfo, pluginList) { }); } - // 资源视图 - adminItems.push({ - name: formatMessage({ id: 'menu.team.platformResources', defaultMessage: '资源视图' }), - icon: getMenuSvg.getSvg('resource'), - path: `team/${teamName}/region/${regionName}/platform-resources/storage`, - authority: ['admin', 'user'] - }); - if (adminItems.length > 0) { menuGroups.push({ groupKey: 'administration', diff --git a/src/locales/en-US/menu.js b/src/locales/en-US/menu.js index 7cc893146..246e4028e 100644 --- a/src/locales/en-US/menu.js +++ b/src/locales/en-US/menu.js @@ -13,7 +13,6 @@ const enterpriseMenu = { 'menu.enterprise.observability': 'Observability', 'menu.enterprise.billing': 'Billing', 'menu.enterprise.plugins': 'Plugins', - 'menu.enterprise.platformResources': 'Platform Resources', // Group titles 'menu.group.basic': 'Basic', 'menu.group.observability': 'Observability', @@ -45,7 +44,6 @@ const teamMenu = { 'menu.team.plugin': 'Plugin', 'menu.team.setting': 'Manage', 'menu.team.pipeline': 'Pipeline', - 'menu.team.platformResources': 'Resources', } const appMenu = { diff --git a/src/locales/zh-CN/menu.js b/src/locales/zh-CN/menu.js index 7f0c872d6..c79335424 100644 --- a/src/locales/zh-CN/menu.js +++ b/src/locales/zh-CN/menu.js @@ -14,7 +14,6 @@ const enterpriseMenu = { 'menu.enterprise.observability': '可观测性', 'menu.enterprise.billing': '计量计费', 'menu.enterprise.plugins': '插件列表', - 'menu.enterprise.platformResources': '平台资源', // 分组标题 'menu.group.basic': '基础功能', 'menu.group.observability': '可观测性', @@ -45,7 +44,6 @@ const teamMenu = { 'menu.team.setting': '团队管理', 'menu.team.pipeline': '流水线', 'menu.team.log': '日志查询', - 'menu.team.platformResources': '资源视图', 'menu.team.link': '链路追踪', 'menu.team.create.wizard': '向导页', } diff --git a/src/models/platformResources.js b/src/models/platformResources.js deleted file mode 100644 index 8309ea7ca..000000000 --- a/src/models/platformResources.js +++ /dev/null @@ -1,97 +0,0 @@ -import { - getPlatformResourcesOverview, - getPlatformStorageClasses, - getPlatformPersistentVolumes, - getPlatformResourceTypes, - getPlatformResourceMetrics, - getPlatformResourceAlerts, -} from '../services/platformResource'; - -export default { - namespace: 'platformResources', - - state: { - overview: {}, - storageClasses: [], - persistentVolumes: [], - resourceTypes: [], - metrics: {}, - alerts: [], - }, - - effects: { - *fetchOverview({ payload, callback, handleError }, { call, put }) { - const response = yield call(getPlatformResourcesOverview, payload, handleError); - if (response) { - yield put({ type: 'saveOverview', payload: response }); - if (callback) callback(response); - } - }, - - *fetchStorageClasses({ payload, callback, handleError }, { call, put }) { - const response = yield call(getPlatformStorageClasses, payload, handleError); - if (response) { - yield put({ type: 'saveStorageClasses', payload: response }); - if (callback) callback(response); - } - }, - - *fetchPersistentVolumes({ payload, callback, handleError }, { call, put }) { - const response = yield call(getPlatformPersistentVolumes, payload, handleError); - if (response) { - yield put({ type: 'savePersistentVolumes', payload: response }); - if (callback) callback(response); - } - }, - - *fetchResourceTypes({ payload, callback, handleError }, { call, put }) { - const response = yield call(getPlatformResourceTypes, payload, handleError); - if (response) { - yield put({ type: 'saveResourceTypes', payload: response }); - if (callback) callback(response); - } - }, - - *fetchMetrics({ payload, callback, handleError }, { call, put }) { - const response = yield call(getPlatformResourceMetrics, payload, handleError); - if (response) { - yield put({ type: 'saveMetrics', payload: response }); - if (callback) callback(response); - } - }, - - *fetchAlerts({ payload, callback, handleError }, { call, put }) { - const response = yield call(getPlatformResourceAlerts, payload, handleError); - if (response) { - yield put({ type: 'saveAlerts', payload: response }); - if (callback) callback(response); - } - }, - }, - - reducers: { - saveOverview(state, action) { - return { ...state, overview: action.payload }; - }, - - saveStorageClasses(state, action) { - return { ...state, storageClasses: action.payload }; - }, - - savePersistentVolumes(state, action) { - return { ...state, persistentVolumes: action.payload }; - }, - - saveResourceTypes(state, action) { - return { ...state, resourceTypes: action.payload }; - }, - - saveMetrics(state, action) { - return { ...state, metrics: action.payload }; - }, - - saveAlerts(state, action) { - return { ...state, alerts: action.payload }; - }, - }, -}; diff --git a/src/pages/PlatformResources/ClusterResources/index.js b/src/pages/PlatformResources/ClusterResources/index.js deleted file mode 100644 index dc4b43bcc..000000000 --- a/src/pages/PlatformResources/ClusterResources/index.js +++ /dev/null @@ -1,324 +0,0 @@ -/* eslint-disable react/sort-comp */ -import React, { PureComponent } from 'react'; -import { connect } from 'dva'; -import { Row, Col, Tree, Table, Button, Drawer, Form, Input, notification, Spin, Empty } from 'antd'; -import { formatMessage } from '@/utils/intl'; -import PageHeaderLayout from '@/layouts/PageHeaderLayout'; -import globalUtil from '../../../utils/global'; -import roleUtil from '../../../utils/newRole'; -import styles from './index.less'; - -const { TreeNode } = Tree; - -@connect(({ teamControl, platformResources }) => ({ - currentTeamPermissionsInfo: teamControl.currentTeamPermissionsInfo, - resourceList: platformResources.resourceList, - resourceTypes: platformResources.resourceTypes, - loading: platformResources.loading, -})) -@Form.create() -class ClusterResources extends PureComponent { - constructor(props) { - super(props); - this.state = { - selectedResourceType: null, - expandedKeys: [], - selectedKeys: [], - drawerVisible: false, - drawerTitle: formatMessage({ id: 'platformResources.cluster.add' }), - drawerType: 'add', - formData: {}, - resourcePermission: roleUtil.queryPermissionsInfo( - this.props.currentTeamPermissionsInfo && this.props.currentTeamPermissionsInfo.team, - 'platform_resources', - 'cluster' - ), - }; - } - - componentDidMount() { - this.fetchResourceTypes(); - } - - fetchResourceTypes = () => { - const { dispatch } = this.props; - dispatch({ - type: 'platformResources/fetchResourceTypes', - payload: { - team_name: globalUtil.getCurrTeamName(), - resource_category: 'cluster', - }, - }); - }; - - fetchResourceList = (resourceType) => { - const { dispatch } = this.props; - dispatch({ - type: 'platformResources/fetchResourceList', - payload: { - team_name: globalUtil.getCurrTeamName(), - resource_type: resourceType, - }, - }); - }; - - handleTreeSelect = (selectedKeys, event) => { - if (selectedKeys.length > 0) { - const resourceType = selectedKeys[0]; - this.setState({ selectedResourceType: resourceType, selectedKeys }); - this.fetchResourceList(resourceType); - } - }; - - handleTreeExpand = (expandedKeys) => { - this.setState({ expandedKeys }); - }; - - handleAddResource = () => { - this.setState({ - drawerVisible: true, - drawerType: 'add', - drawerTitle: formatMessage({ id: 'platformResources.cluster.add' }), - formData: {}, - }); - }; - - handleEditResource = (record) => { - this.setState({ - drawerVisible: true, - drawerType: 'edit', - drawerTitle: formatMessage({ id: 'platformResources.cluster.edit' }), - formData: record, - }); - }; - - handleDeleteResource = (record) => { - const { dispatch } = this.props; - const { selectedResourceType } = this.state; - dispatch({ - type: 'platformResources/deleteResource', - payload: { - team_name: globalUtil.getCurrTeamName(), - resource_id: record.id, - }, - callback: () => { - notification.success({ - message: formatMessage({ id: 'platformResources.cluster.deleteSuccess' }), - }); - this.fetchResourceList(selectedResourceType); - }, - }); - }; - - handleDrawerClose = () => { - this.setState({ drawerVisible: false }); - }; - - handleDrawerSubmit = () => { - const { form, dispatch } = this.props; - const { drawerType, selectedResourceType, formData } = this.state; - - form.validateFields((err, values) => { - if (!err) { - const payload = { - team_name: globalUtil.getCurrTeamName(), - resource_type: selectedResourceType, - ...values, - }; - - if (drawerType === 'edit') { - payload.resource_id = formData.id; - } - - const actionType = drawerType === 'add' - ? 'platformResources/createResource' - : 'platformResources/updateResource'; - - dispatch({ - type: actionType, - payload, - callback: () => { - notification.success({ - message: formatMessage({ - id: drawerType === 'add' - ? 'platformResources.cluster.addSuccess' - : 'platformResources.cluster.editSuccess', - }), - }); - this.handleDrawerClose(); - this.fetchResourceList(selectedResourceType); - }, - }); - } - }); - }; - - renderResourceTree = () => { - const { resourceTypes } = this.props; - const { expandedKeys } = this.state; - - if (!resourceTypes || resourceTypes.length === 0) { - return ; - } - - return ( - - {resourceTypes.map((type) => ( - - ))} - - ); - }; - - renderResourceTable = () => { - const { resourceList, loading } = this.props; - const { resourcePermission } = this.state; - - const columns = [ - { - title: formatMessage({ id: 'platformResources.cluster.name' }), - dataIndex: 'name', - key: 'name', - }, - { - title: formatMessage({ id: 'platformResources.cluster.type' }), - dataIndex: 'type', - key: 'type', - }, - { - title: formatMessage({ id: 'platformResources.cluster.status' }), - dataIndex: 'status', - key: 'status', - render: (status) => { - const statusMap = { - active: formatMessage({ id: 'platformResources.cluster.statusActive' }), - inactive: formatMessage({ id: 'platformResources.cluster.statusInactive' }), - }; - return statusMap[status] || status; - }, - }, - { - title: formatMessage({ id: 'platformResources.cluster.action' }), - key: 'action', - render: (text, record) => ( - - {resourcePermission && resourcePermission.edit && ( - <> - this.handleEditResource(record)}> - {formatMessage({ id: 'platformResources.cluster.edit' })} - - | - - )} - {resourcePermission && resourcePermission.delete && ( - this.handleDeleteResource(record)}> - {formatMessage({ id: 'platformResources.cluster.delete' })} - - )} - - ), - }, - ]; - - return ( - -
- - ); - }; - - renderDrawer = () => { - const { form } = this.props; - const { drawerVisible, drawerTitle, drawerType, formData } = this.state; - const { getFieldDecorator } = form; - - return ( - -
- - {getFieldDecorator('name', { - initialValue: formData.name || '', - rules: [ - { - required: true, - message: formatMessage({ id: 'platformResources.cluster.nameRequired' }), - }, - ], - })()} - - - - {getFieldDecorator('description', { - initialValue: formData.description || '', - })()} - - - - - - - -
- ); - }; - - render() { - const { resourcePermission } = this.state; - - return ( - -
- -
-
-

{formatMessage({ id: 'platformResources.cluster.resourceTypes' })}

-
-
- {this.renderResourceTree()} -
- -
-
-

{formatMessage({ id: 'platformResources.cluster.resourceList' })}

- {resourcePermission && resourcePermission.add && ( - - )} -
-
- {this.renderResourceTable()} -
- - - {this.renderDrawer()} - - - ); - } -} - -export default ClusterResources; diff --git a/src/pages/PlatformResources/ClusterResources/index.less b/src/pages/PlatformResources/ClusterResources/index.less deleted file mode 100644 index f39bcc18d..000000000 --- a/src/pages/PlatformResources/ClusterResources/index.less +++ /dev/null @@ -1,97 +0,0 @@ -@import '~antd/lib/style/themes/default.less'; - -.container { - padding: 20px; - background-color: @rbd-background-color; - min-height: 100vh; -} - -.treePanel { - background-color: #fff; - border-radius: 4px; - box-shadow: @rbd-min-card-shadow; - padding: 16px; -} - -.treeHeader { - margin-bottom: 16px; - border-bottom: 1px solid @border-color-base; - padding-bottom: 12px; - - h3 { - margin: 0; - font-size: @rbd-sub-title-size; - color: @heading-color; - font-weight: 500; - } -} - -.treeContent { - max-height: 600px; - overflow-y: auto; - - :global { - .ant-tree { - background-color: transparent; - } - - .ant-tree-node-content-wrapper { - color: @text-color; - - &:hover { - background-color: #f5f5f5; - } - } - - .ant-tree-node-selected .ant-tree-node-content-wrapper { - background-color: @primary-color; - color: #fff; - } - } -} - -.listPanel { - background-color: #fff; - border-radius: 4px; - box-shadow: @rbd-min-card-shadow; - padding: 16px; -} - -.listHeader { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 16px; - border-bottom: 1px solid @border-color-base; - padding-bottom: 12px; - - h3 { - margin: 0; - font-size: @rbd-sub-title-size; - color: @heading-color; - font-weight: 500; - } -} - -.listContent { - :global { - .ant-table { - background-color: transparent; - } - - .ant-table-thead > tr > th { - background-color: #fafafa; - color: @heading-color; - font-weight: 500; - } - - .ant-table-tbody > tr:hover > td { - background-color: #f5f5f5; - } - } -} - -.divider { - margin: 0 8px; - color: @border-color-base; -} diff --git a/src/pages/PlatformResources/Storage/index.js b/src/pages/PlatformResources/Storage/index.js deleted file mode 100644 index 0356ebdee..000000000 --- a/src/pages/PlatformResources/Storage/index.js +++ /dev/null @@ -1,337 +0,0 @@ -import React, { PureComponent } from 'react'; -import { Card, Row, Col, Table, Spin, Button, Modal, notification } from 'antd'; -import { connect } from 'dva'; -import { formatMessage } from '@/utils/intl'; -import globalUtil from '@/utils/global'; -import styles from './index.less'; - -@connect(({ loading, platformResources }) => ({ - storageLoading: loading.effects['platformResources/fetchStorageData'], - storageClassList: platformResources.storageClassList || [], - pvList: platformResources.pvList || [], - storageStats: platformResources.storageStats || {} -})) -export default class StoragePage extends PureComponent { - constructor(props) { - super(props); - this.state = { - selectedStorageClass: null, - selectedPV: null - }; - } - - componentDidMount() { - this.loadStorageData(); - } - - loadStorageData = () => { - const { dispatch } = this.props; - const regionName = globalUtil.getCurrRegionName(); - - dispatch({ - type: 'platformResources/fetchStorageData', - payload: { - region_name: regionName - }, - callback: res => { - if (res && res.status_code !== 200) { - notification.error({ - message: formatMessage({ id: 'notification.error.load_storage' }) - }); - } - } - }); - }; - - handleRefresh = () => { - this.loadStorageData(); - }; - - handleDeleteStorageClass = (record) => { - const { dispatch } = this.props; - const regionName = globalUtil.getCurrRegionName(); - - Modal.confirm({ - title: formatMessage({ id: 'modal.confirm.delete' }), - content: formatMessage({ id: 'modal.confirm.delete_storage_class' }), - okText: formatMessage({ id: 'button.confirm' }), - cancelText: formatMessage({ id: 'button.cancel' }), - onOk: () => { - dispatch({ - type: 'platformResources/deleteStorageClass', - payload: { - region_name: regionName, - name: record.name - }, - callback: res => { - if (res && res.status_code === 200) { - notification.success({ - message: formatMessage({ id: 'notification.success.delete' }) - }); - this.loadStorageData(); - } - } - }); - } - }); - }; - - handleDeletePV = (record) => { - const { dispatch } = this.props; - const regionName = globalUtil.getCurrRegionName(); - - Modal.confirm({ - title: formatMessage({ id: 'modal.confirm.delete' }), - content: formatMessage({ id: 'modal.confirm.delete_pv' }), - okText: formatMessage({ id: 'button.confirm' }), - cancelText: formatMessage({ id: 'button.cancel' }), - onOk: () => { - dispatch({ - type: 'platformResources/deletePV', - payload: { - region_name: regionName, - name: record.name - }, - callback: res => { - if (res && res.status_code === 200) { - notification.success({ - message: formatMessage({ id: 'notification.success.delete' }) - }); - this.loadStorageData(); - } - } - }); - } - }); - }; - - renderStorageStats = () => { - const { storageStats } = this.props; - - const stats = [ - { - label: formatMessage({ id: 'storage.stats.total_capacity' }), - value: storageStats.totalCapacity || '0 Gi', - color: '#155aef' - }, - { - label: formatMessage({ id: 'storage.stats.used_capacity' }), - value: storageStats.usedCapacity || '0 Gi', - color: '#18B633' - }, - { - label: formatMessage({ id: 'storage.stats.available_capacity' }), - value: storageStats.availableCapacity || '0 Gi', - color: '#FF8D3C' - }, - { - label: formatMessage({ id: 'storage.stats.storage_class_count' }), - value: storageStats.storageClassCount || 0, - color: '#FC481B' - } - ]; - - return ( - - {stats.map((stat, index) => ( -
- -
{stat.label}
-
- {stat.value} -
-
- - ))} - - ); - }; - - renderStorageClassTable = () => { - const { storageClassList, storageLoading } = this.props; - - const columns = [ - { - title: formatMessage({ id: 'storage.table.name' }), - dataIndex: 'name', - key: 'name', - width: '20%' - }, - { - title: formatMessage({ id: 'storage.table.provisioner' }), - dataIndex: 'provisioner', - key: 'provisioner', - width: '20%' - }, - { - title: formatMessage({ id: 'storage.table.reclaim_policy' }), - dataIndex: 'reclaimPolicy', - key: 'reclaimPolicy', - width: '15%' - }, - { - title: formatMessage({ id: 'storage.table.allow_volume_expansion' }), - dataIndex: 'allowVolumeExpansion', - key: 'allowVolumeExpansion', - width: '15%', - render: (text) => (text ? formatMessage({ id: 'common.yes' }) : formatMessage({ id: 'common.no' })) - }, - { - title: formatMessage({ id: 'storage.table.pv_count' }), - dataIndex: 'pvCount', - key: 'pvCount', - width: '10%' - }, - { - title: formatMessage({ id: 'common.action' }), - key: 'action', - width: '20%', - render: (text, record) => ( -
- -
- ) - } - ]; - - return ( - {formatMessage({ id: 'button.refresh' })}} - className={styles.tableCard} - > -
- - ); - }; - - renderPVTable = () => { - const { pvList, storageLoading } = this.props; - - const columns = [ - { - title: formatMessage({ id: 'storage.table.name' }), - dataIndex: 'name', - key: 'name', - width: '15%' - }, - { - title: formatMessage({ id: 'storage.table.storage_class' }), - dataIndex: 'storageClassName', - key: 'storageClassName', - width: '15%' - }, - { - title: formatMessage({ id: 'storage.table.capacity' }), - dataIndex: 'capacity', - key: 'capacity', - width: '12%' - }, - { - title: formatMessage({ id: 'storage.table.access_modes' }), - dataIndex: 'accessModes', - key: 'accessModes', - width: '15%', - render: (modes) => (modes ? modes.join(', ') : '-') - }, - { - title: formatMessage({ id: 'storage.table.status' }), - dataIndex: 'status', - key: 'status', - width: '12%', - render: (status) => { - let color = '#676f83'; - if (status === 'Bound') { - color = '#18B633'; - } else if (status === 'Available') { - color = '#155aef'; - } else if (status === 'Released') { - color = '#FF8D3C'; - } else if (status === 'Failed') { - color = '#FC481B'; - } - return {status}; - } - }, - { - title: formatMessage({ id: 'storage.table.claim' }), - dataIndex: 'claimRef', - key: 'claimRef', - width: '15%', - render: (claim) => (claim ? `${claim.namespace}/${claim.name}` : '-') - }, - { - title: formatMessage({ id: 'common.action' }), - key: 'action', - width: '16%', - render: (text, record) => ( -
- -
- ) - } - ]; - - return ( - {formatMessage({ id: 'button.refresh' })}} - className={styles.tableCard} - > -
- - ); - }; - - render() { - const { storageLoading } = this.props; - - return ( - -
-
-

{formatMessage({ id: 'storage.page.title' })}

-
- - {this.renderStorageStats()} - - -
- {this.renderStorageClassTable()} - - - - - - {this.renderPVTable()} - - - - - ); - } -} diff --git a/src/pages/PlatformResources/Storage/index.less b/src/pages/PlatformResources/Storage/index.less deleted file mode 100644 index dc0fb3ab9..000000000 --- a/src/pages/PlatformResources/Storage/index.less +++ /dev/null @@ -1,100 +0,0 @@ -@import '~antd/lib/style/themes/default.less'; - -.storageContainer { - padding: 24px; - background-color: @rbd-background-color; - min-height: 100vh; -} - -.header { - margin-bottom: 24px; - - h2 { - font-size: @rbd-title-size; - color: @heading-color; - margin: 0; - font-weight: 500; - } -} - -.statsRow { - margin-bottom: 24px; -} - -.statCard { - border-radius: 4px; - box-shadow: @rbd-min-card-shadow; - transition: all 0.3s ease; - - &:hover { - box-shadow: @rbd-card-shadow-hover; - } -} - -.statLabel { - font-size: @rbd-auxiliary-size; - color: @text-color-secondary; - margin-bottom: 12px; - font-weight: 400; -} - -.statValue { - font-size: @rbd-title-big-size; - font-weight: 600; - line-height: 1.2; -} - -.tableCard { - border-radius: 4px; - box-shadow: @rbd-min-card-shadow; - margin-bottom: 24px; - - :global { - .ant-card-head { - border-bottom: 1px solid @border-color-base; - padding: 16px 24px; - } - - .ant-card-body { - padding: 0; - } - - .ant-table { - font-size: @rbd-content-size; - } - - .ant-table-thead > tr > th { - background-color: #fafafa; - color: @heading-color; - font-weight: 600; - border-bottom: 1px solid @border-color-base; - } - - .ant-table-tbody > tr { - &:hover > td { - background-color: #f5f5f5; - } - } - - .ant-table-tbody > tr > td { - border-bottom: 1px solid @border-color-base; - padding: 12px 16px; - } - - .ant-btn-link { - color: @primary-color; - padding: 0; - height: auto; - - &:hover { - color: darken(@primary-color, 10%); - } - } - - .ant-pagination { - margin: 16px 0; - text-align: right; - padding-right: 16px; - } - } -} diff --git a/src/services/platformResource.js b/src/services/platformResource.js deleted file mode 100644 index ac9158910..000000000 --- a/src/services/platformResource.js +++ /dev/null @@ -1,98 +0,0 @@ -import apiconfig from '../../config/api.config'; -import request from '../utils/request'; - -/** - * Get storage overview information - */ -export async function getStorageOverview(body = {}) { - return request( - `${apiconfig.baseUrl}/console/platform/storage/overview`, - { - method: 'get', - params: { - cluster_id: body.cluster_id - } - } - ); -} - -/** - * Get available storage classes - */ -export async function getStorageClasses(body = {}) { - return request( - `${apiconfig.baseUrl}/console/platform/storage/classes`, - { - method: 'get', - params: { - cluster_id: body.cluster_id - } - } - ); -} - -/** - * Get persistent volumes - */ -export async function getPersistentVolumes(body = {}) { - return request( - `${apiconfig.baseUrl}/console/platform/storage/volumes`, - { - method: 'get', - params: { - cluster_id: body.cluster_id, - page: body.page, - page_size: body.page_size - } - } - ); -} - -/** - * Get cluster resource types - */ -export async function getClusterResourceTypes(body = {}) { - return request( - `${apiconfig.baseUrl}/console/platform/cluster/resource-types`, - { - method: 'get', - params: { - cluster_id: body.cluster_id - } - } - ); -} - -/** - * Get cluster resources - */ -export async function getClusterResources(body = {}) { - return request( - `${apiconfig.baseUrl}/console/platform/cluster/resources`, - { - method: 'get', - params: { - cluster_id: body.cluster_id, - resource_type: body.resource_type, - page: body.page, - page_size: body.page_size - } - } - ); -} - -/** - * Get cluster resource detail - */ -export async function getClusterResourceDetail(body = {}) { - return request( - `${apiconfig.baseUrl}/console/platform/cluster/resources/${body.resource_id}`, - { - method: 'get', - params: { - cluster_id: body.cluster_id, - resource_type: body.resource_type - } - } - ); -} From c4619a857441f8f7720b9ad084f2904f09d80a4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=AF=E8=88=AA?= <12344192+zhangsetsail@user.noreply.gitee.com> Date: Tue, 17 Mar 2026 12:37:21 +0800 Subject: [PATCH 007/147] feat: add global switch UI and menu entries --- config/router.config.js | 13 ++ src/common/enterpriseMenu.js | 11 ++ src/common/getMenuSvg.js | 5 + src/common/teamMenu.js | 21 ++- src/layouts/TeamLayout.js | 4 +- src/locales/en-US/menu.js | 2 + src/locales/zh-CN/menu.js | 2 + src/models/platformResources.js | 55 +++++++ src/models/teamResources.js | 52 ++++++ src/pages/PlatformResources/index.js | 147 +++++++++++++++++ src/pages/ResourceCenter/index.js | 230 +++++++++++++++++++++++++++ src/services/platformResource.js | 42 +++++ src/services/platformSettings.js | 19 +++ src/services/teamResource.js | 52 ++++++ 14 files changed, 649 insertions(+), 6 deletions(-) create mode 100644 src/models/platformResources.js create mode 100644 src/models/teamResources.js create mode 100644 src/pages/PlatformResources/index.js create mode 100644 src/pages/ResourceCenter/index.js create mode 100644 src/services/platformResource.js create mode 100644 src/services/platformSettings.js create mode 100644 src/services/teamResource.js diff --git a/config/router.config.js b/config/router.config.js index e0751d146..22f97ebc0 100644 --- a/config/router.config.js +++ b/config/router.config.js @@ -161,6 +161,12 @@ export default [ name: 'LogManagement', authority: ['admin', 'user'] }, + { + path: '/enterprise/:eid/region/:regionName/platform-resources', + component: './PlatformResources', + name: 'PlatformResources', + authority: ['admin', 'user'] + }, { path: '/enterprise/:eid/extension', component: './Extension', @@ -340,6 +346,13 @@ export default [ authority: ['admin', 'user'], title: '流水线' }, + { + path: '/team/:teamName/region/:regionName/resource-center', + component: './ResourceCenter', + name: 'ResourceCenter', + authority: ['admin', 'user'], + title: '资源中心' + }, { path: '/team/:teamName/region/:regionName/apps/:appID/upgrade', component: './Upgrade', diff --git a/src/common/enterpriseMenu.js b/src/common/enterpriseMenu.js index d327a94b3..908a042e3 100644 --- a/src/common/enterpriseMenu.js +++ b/src/common/enterpriseMenu.js @@ -67,6 +67,17 @@ function menuData(eid, currentUser, enterprise, pluginList, clusterList) { } ]; + // 平台资源(需要至少一个集群) + if (clusterList && clusterList.length > 0) { + const firstCluster = clusterList[0]; + resourceItems.push({ + name: formatMessage({ id: 'menu.enterprise.platform_resources', defaultMessage: '平台资源' }), + icon: getMenuSvg.getSvg('resource'), + path: `/enterprise/${eid}/region/${firstCluster.region_name}/platform-resources`, + authority: ['admin', 'user'] + }); + } + // 计量计费 const billPlugin = PluginUtil.getPluginInfo(pluginList, 'rainbond-bill'); if (billPlugin && Object.keys(billPlugin).length !== 0) { diff --git a/src/common/getMenuSvg.js b/src/common/getMenuSvg.js index ea2062b18..c104b0b39 100644 --- a/src/common/getMenuSvg.js +++ b/src/common/getMenuSvg.js @@ -172,6 +172,11 @@ const getMenuSvg = { + ), + resource: ( + + + ) } return svg[type] || type; diff --git a/src/common/teamMenu.js b/src/common/teamMenu.js index 0bac8aa68..283bcffb2 100644 --- a/src/common/teamMenu.js +++ b/src/common/teamMenu.js @@ -22,9 +22,10 @@ function setTeamMenu(pluginMenu, menuName) { * @param {string} regionName - 集群名称 * @param {object} permissionsInfo - 权限信息 * @param {array} pluginList - 插件列表 + * @param {object} enterpriseSettings - 企业平台设置(可选) * @returns {array} 分组菜单数组 */ -function menuData(teamName, regionName, permissionsInfo, pluginList) { +function menuData(teamName, regionName, permissionsInfo, pluginList, enterpriseSettings) { const menuGroups = []; function results() { @@ -92,6 +93,16 @@ function menuData(teamName, regionName, permissionsInfo, pluginList) { }); } + // 资源中心(需要平台开启 enable_team_resource_view) + if (enterpriseSettings && enterpriseSettings.enable_team_resource_view) { + adminItems.push({ + name: formatMessage({ id: 'menu.team.resource_center', defaultMessage: '资源中心' }), + icon: getMenuSvg.getSvg('resource'), + path: `team/${teamName}/region/${regionName}/resource-center`, + authority: ['admin', 'user'] + }); + } + if (adminItems.length > 0) { menuGroups.push({ groupKey: 'administration', @@ -158,15 +169,15 @@ function formatter(menuGroups) { /** * 获取分组菜单数据 */ -export const getMenuData = (teamName, regionName, permissionsInfo, pluginList) => { - const menuGroups = menuData(teamName, regionName, permissionsInfo, pluginList); +export const getMenuData = (teamName, regionName, permissionsInfo, pluginList, enterpriseSettings) => { + const menuGroups = menuData(teamName, regionName, permissionsInfo, pluginList, enterpriseSettings); return formatter(menuGroups); }; /** * 将分组菜单展平为普通菜单数组(兼容旧代码) */ -export const getFlatMenuData = (teamName, regionName, permissionsInfo, pluginList) => { - const menuGroups = getMenuData(teamName, regionName, permissionsInfo, pluginList); +export const getFlatMenuData = (teamName, regionName, permissionsInfo, pluginList, enterpriseSettings) => { + const menuGroups = getMenuData(teamName, regionName, permissionsInfo, pluginList, enterpriseSettings); return menuGroups.reduce((acc, group) => [...acc, ...group.items], []); }; diff --git a/src/layouts/TeamLayout.js b/src/layouts/TeamLayout.js index aa34458a6..f8eb188c4 100644 --- a/src/layouts/TeamLayout.js +++ b/src/layouts/TeamLayout.js @@ -875,11 +875,13 @@ class TeamLayout extends PureComponent { ); } } + const safeEnterpriseForMenu = enterprise || {}; let menuData = getMenuData( teamName, regionName, currentTeam.tenant_actions, - showPipeline + showPipeline, + safeEnterpriseForMenu ); if (mode === 'app') { menuData = getAppMenuData( diff --git a/src/locales/en-US/menu.js b/src/locales/en-US/menu.js index 246e4028e..a61107c90 100644 --- a/src/locales/en-US/menu.js +++ b/src/locales/en-US/menu.js @@ -13,6 +13,8 @@ const enterpriseMenu = { 'menu.enterprise.observability': 'Observability', 'menu.enterprise.billing': 'Billing', 'menu.enterprise.plugins': 'Plugins', + 'menu.enterprise.platform_resources': 'Platform Resources', + 'menu.team.resource_center': 'Resource Center', // Group titles 'menu.group.basic': 'Basic', 'menu.group.observability': 'Observability', diff --git a/src/locales/zh-CN/menu.js b/src/locales/zh-CN/menu.js index c79335424..2e4568807 100644 --- a/src/locales/zh-CN/menu.js +++ b/src/locales/zh-CN/menu.js @@ -14,6 +14,8 @@ const enterpriseMenu = { 'menu.enterprise.observability': '可观测性', 'menu.enterprise.billing': '计量计费', 'menu.enterprise.plugins': '插件列表', + 'menu.enterprise.platform_resources': '平台资源', + 'menu.team.resource_center': '资源中心', // 分组标题 'menu.group.basic': '基础功能', 'menu.group.observability': '可观测性', diff --git a/src/models/platformResources.js b/src/models/platformResources.js new file mode 100644 index 000000000..69f8c8901 --- /dev/null +++ b/src/models/platformResources.js @@ -0,0 +1,55 @@ +import { + listStorageClasses, + createStorageClass, + deleteStorageClass, + listPersistentVolumes, + listPlatformResources, + deletePlatformResource, +} from '../services/platformResource'; + +export default { + namespace: 'platformResources', + state: { + storageClasses: [], + persistentVolumes: [], + platformResources: [], + total: 0, + }, + effects: { + *fetchStorageClasses({ payload }, { call, put }) { + const res = yield call(listStorageClasses, payload); + if (res && res.bean) { + yield put({ type: 'save', payload: { storageClasses: res.bean.list || [] } }); + } + }, + *createStorageClass({ payload, callback }, { call }) { + const res = yield call(createStorageClass, payload); + if (res && callback) callback(res); + }, + *deleteStorageClass({ payload, callback }, { call }) { + const res = yield call(deleteStorageClass, payload); + if (res && callback) callback(res); + }, + *fetchPersistentVolumes({ payload }, { call, put }) { + const res = yield call(listPersistentVolumes, payload); + if (res && res.bean) { + yield put({ type: 'save', payload: { persistentVolumes: res.bean.list || [] } }); + } + }, + *fetchPlatformResources({ payload }, { call, put }) { + const res = yield call(listPlatformResources, payload); + if (res && res.bean) { + yield put({ type: 'save', payload: { platformResources: res.bean.list || [], total: res.bean.total || 0 } }); + } + }, + *deletePlatformResource({ payload, callback }, { call }) { + const res = yield call(deletePlatformResource, payload); + if (res && callback) callback(res); + }, + }, + reducers: { + save(state, { payload }) { + return { ...state, ...payload }; + }, + }, +}; diff --git a/src/models/teamResources.js b/src/models/teamResources.js new file mode 100644 index 000000000..84becafff --- /dev/null +++ b/src/models/teamResources.js @@ -0,0 +1,52 @@ +import { + listNsResources, + createNsResource, + deleteNsResource, + listHelmReleases, + installHelmRelease, + uninstallHelmRelease, +} from '../services/teamResource'; + +export default { + namespace: 'teamResources', + state: { + resources: [], + helmReleases: [], + total: 0, + }, + effects: { + *fetchResources({ payload }, { call, put }) { + const res = yield call(listNsResources, payload); + if (res && res.bean) { + yield put({ type: 'save', payload: { resources: res.bean.list || [], total: res.bean.total || 0 } }); + } + }, + *createResource({ payload, callback }, { call }) { + const res = yield call(createNsResource, payload); + if (callback) callback(res); + }, + *deleteResource({ payload, callback }, { call }) { + const res = yield call(deleteNsResource, payload); + if (callback) callback(res); + }, + *fetchHelmReleases({ payload }, { call, put }) { + const res = yield call(listHelmReleases, payload); + if (res && res.bean) { + yield put({ type: 'save', payload: { helmReleases: res.bean.list || [] } }); + } + }, + *installRelease({ payload, callback }, { call }) { + const res = yield call(installHelmRelease, payload); + if (callback) callback(res); + }, + *uninstallRelease({ payload, callback }, { call }) { + const res = yield call(uninstallHelmRelease, payload); + if (callback) callback(res); + }, + }, + reducers: { + save(state, { payload }) { + return { ...state, ...payload }; + }, + }, +}; diff --git a/src/pages/PlatformResources/index.js b/src/pages/PlatformResources/index.js new file mode 100644 index 000000000..b2a403582 --- /dev/null +++ b/src/pages/PlatformResources/index.js @@ -0,0 +1,147 @@ +import React, { PureComponent } from 'react'; +import { connect } from 'dva'; +import { Tabs, Table, Button, Modal, Input, Tag, Popconfirm } from 'antd'; +import { formatMessage } from '@/utils/intl'; + +const { TabPane } = Tabs; +const { TextArea } = Input; + +@connect(({ platformResources }) => ({ + storageClasses: platformResources.storageClasses, + persistentVolumes: platformResources.persistentVolumes, +})) +class PlatformResources extends PureComponent { + state = { + createModalVisible: false, + yamlContent: '', + createTarget: '', + }; + + componentDidMount() { + this.fetchStorageClasses(); + } + + fetchStorageClasses = () => { + const { dispatch, match } = this.props; + const { eid, regionName } = match.params; + dispatch({ + type: 'platformResources/fetchStorageClasses', + payload: { eid, region: regionName }, + }); + }; + + handleDeleteStorageClass = (name) => { + const { dispatch, match } = this.props; + const { eid, regionName } = match.params; + dispatch({ + type: 'platformResources/deleteStorageClass', + payload: { eid, region: regionName, name }, + callback: () => this.fetchStorageClasses(), + }); + }; + + handleCreateConfirm = () => { + const { dispatch, match } = this.props; + const { eid, regionName } = match.params; + const { yamlContent, createTarget } = this.state; + dispatch({ + type: `platformResources/create${createTarget}`, + payload: { eid, region: regionName, yaml: yamlContent }, + callback: () => { + this.setState({ createModalVisible: false, yamlContent: '' }); + this.fetchStorageClasses(); + }, + }); + }; + + renderStorageClassTab() { + const { storageClasses } = this.props; + const columns = [ + { title: '名称', dataIndex: 'name', key: 'name' }, + { title: 'Provisioner', dataIndex: 'provisioner', key: 'provisioner' }, + { title: '默认', dataIndex: 'is_default', key: 'is_default', + render: v => v ? 默认 : null }, + { title: '回收策略', dataIndex: 'reclaim_policy', key: 'reclaim_policy' }, + { title: '绑定模式', dataIndex: 'volume_binding_mode', key: 'volume_binding_mode' }, + { title: '卷数', dataIndex: 'pv_count', key: 'pv_count' }, + { + title: '操作', key: 'action', + render: (_, record) => ( + this.handleDeleteStorageClass(record.name)}> + 删除 + + ), + }, + ]; + return ( +
+
+ +
+
+ + ); + } + + render() { + const { createModalVisible, yamlContent } = this.state; + return ( +
+

平台资源

+

全局资源与集群级公共资源管理

+ + +

集群资源概览(待实现)

+
+ + + + {this.renderStorageClassTab()} + + +

存储卷列表(待实现)

+
+
+
+ +

ClusterRole / ClusterRoleBinding(待实现)

+
+ +

CRD 列表(待实现)

+
+ +

Namespace 列表(待实现)

+
+
+ + this.setState({ createModalVisible: false })} + width={640} + > +