diff --git a/src/locales/en-US/resource.js b/src/locales/en-US/resource.js
index b4730bfd6..ad929566f 100644
--- a/src/locales/en-US/resource.js
+++ b/src/locales/en-US/resource.js
@@ -126,13 +126,19 @@ const resource = {
'resourceCenter.tab.pod.emptyHint': 'If the list is empty, check whether workloads are created successfully and replicas are starting as expected.',
'resourceCenter.tab.network.title': 'Networking',
- 'resourceCenter.tab.network.navDescription': 'Manage Service type, ports, and selectors',
+ 'resourceCenter.tab.network.navDescription': 'Manage Services, Ingresses, and traffic entry points',
'resourceCenter.tab.network.description': 'Manage traffic exposure in the team namespace and identify ports and entry points quickly.',
'resourceCenter.tab.network.listTitle': 'Network Resource List',
- 'resourceCenter.tab.network.listDescription': 'Review service type, port exposure, and selector bindings, and edit YAML directly.',
+ 'resourceCenter.tab.network.listDescription': 'Review service type, port exposure, Ingress hosts, selector bindings, and edit YAML directly.',
'resourceCenter.tab.network.emptyTitle': 'No Network Resources Yet',
- 'resourceCenter.tab.network.emptyDescription': 'After creating a Service with YAML, you can review ClusterIP, ports, and selectors here.',
- 'resourceCenter.tab.network.emptyHint': 'If you need to expose traffic, create the workload first and then define the matching Service.',
+ 'resourceCenter.tab.network.emptyDescription': 'After creating a Service or Ingress with YAML, ports, hosts, and selectors appear here.',
+ 'resourceCenter.tab.network.emptyHint': 'If you need to expose traffic, create the workload first and then define the matching Service or Ingress.',
+ 'resourceCenter.tab.network.services': 'Services',
+ 'resourceCenter.tab.network.ingresses': 'Ingresses',
+ 'resourceCenter.tab.network.ingressClass': 'IngressClass',
+ 'resourceCenter.tab.network.hosts': 'Hosts',
+ 'resourceCenter.tab.network.tlsHosts': 'TLS Hosts',
+ 'resourceCenter.tab.network.backendServices': 'Backend Services',
'resourceCenter.tab.config.title': 'Configuration',
'resourceCenter.tab.config.navDescription': 'Unified view for ConfigMaps and Secrets',
@@ -180,11 +186,11 @@ const resource = {
'resourceCenter.metrics.pod.errorHelper': 'Pods that need logs or events checked',
'resourceCenter.metrics.network.total': 'Network Objects',
- 'resourceCenter.metrics.network.totalHelper': 'Service resources in the current team',
+ 'resourceCenter.metrics.network.totalHelper': 'Service and Ingress resources in the current team',
'resourceCenter.metrics.network.ports': 'Exposed Ports',
'resourceCenter.metrics.network.portsHelper': 'Total declared ports and protocols',
'resourceCenter.metrics.network.exposed': 'Externally Exposed',
- 'resourceCenter.metrics.network.exposedHelper': 'Services that are not ClusterIP',
+ 'resourceCenter.metrics.network.exposedHelper': 'Services that are not ClusterIP plus Ingress entry points',
'resourceCenter.metrics.network.selectorless': 'No Selector',
'resourceCenter.metrics.network.selectorlessHelper': 'Requires manual confirmation of traffic binding',
@@ -219,6 +225,7 @@ const resource = {
'resourceCenter.yaml.toolbar.createHint': 'Supports `.yaml` / `.yml`; you can continue reviewing and editing after import.',
'resourceCenter.yaml.toolbar.multiDoc': 'Multi-document supported',
'resourceCenter.yaml.toolbar.manualEdit': 'Manual editing preserved',
+ 'resourceCenter.yaml.nativeResourceNotice': 'This creates Kubernetes native resources. After creation, view and manage them in K8S Native Resources; they will not appear in Application Management automatically. To create applications or components for Application Management, use the Application Management entry.',
'resourceCenter.yaml.placeholder': 'apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: my-app\n...',
'resourceCenter.yaml.ok.create': 'Create',
'resourceCenter.yaml.ok.edit': 'Save',
@@ -305,6 +312,7 @@ const resource = {
'resourceCenter.helm.modal.tabExternalHelper': 'Supports official, self-hosted repos, and OCI',
'resourceCenter.helm.modal.tabUpload': 'Upload Chart Package',
'resourceCenter.helm.modal.tabUploadHelper': 'Upload a .tgz and install the release directly',
+ 'resourceCenter.helm.modal.nativeResourceNotice': 'This installs a Helm release and manages it as Kubernetes native resources in K8S Native Resources. It will not appear in Application Management automatically. To create applications or components for Application Management, use the Application Management entry.',
'resourceCenter.helm.modal.externalNotice': 'Provide a chart address directly. Helm repo package URLs and OCI artifact addresses are both supported. Validation starts automatically when you go to the next step.',
'resourceCenter.helm.modal.externalNoticeShort': 'Provide a chart address directly. Helm repo package URLs and OCI artifact addresses are supported.',
'resourceCenter.helm.modal.externalSupport': 'Supports official Helm repos, self-hosted Helm repos, and OCI artifact sources.',
diff --git a/src/locales/zh-CN/resource.js b/src/locales/zh-CN/resource.js
index aea2b084d..15946306a 100644
--- a/src/locales/zh-CN/resource.js
+++ b/src/locales/zh-CN/resource.js
@@ -126,13 +126,19 @@ const resource = {
'resourceCenter.tab.pod.emptyHint': '如果这里为空,可以先检查工作负载是否创建完成或副本是否正常拉起。',
'resourceCenter.tab.network.title': '网络',
- 'resourceCenter.tab.network.navDescription': 'Service 类型、端口与选择器管理',
+ 'resourceCenter.tab.network.navDescription': 'Service、Ingress 与流量入口管理',
'resourceCenter.tab.network.description': '管理团队命名空间下的网络暴露方式,快速识别端口与流量入口。',
'resourceCenter.tab.network.listTitle': '网络资源清单',
- 'resourceCenter.tab.network.listDescription': '查看 Service 类型、端口暴露、Selector 绑定关系,并支持直接编辑 YAML。',
+ 'resourceCenter.tab.network.listDescription': '查看 Service 类型、端口暴露、Ingress 域名入口与 Selector 绑定关系,并支持直接编辑 YAML。',
'resourceCenter.tab.network.emptyTitle': '还没有网络资源',
- 'resourceCenter.tab.network.emptyDescription': '通过 YAML 新建 Service 后,可以在这里集中查看 ClusterIP、端口和选择器信息。',
- 'resourceCenter.tab.network.emptyHint': '如果要暴露服务,建议先创建工作负载,再补充对应的 Service 定义。',
+ 'resourceCenter.tab.network.emptyDescription': '通过 YAML 新建 Service 或 Ingress 后,可以在这里集中查看端口、域名入口和选择器信息。',
+ 'resourceCenter.tab.network.emptyHint': '如果要暴露服务,建议先创建工作负载,再补充对应的 Service 或 Ingress 定义。',
+ 'resourceCenter.tab.network.services': 'Services',
+ 'resourceCenter.tab.network.ingresses': 'Ingresses',
+ 'resourceCenter.tab.network.ingressClass': 'IngressClass',
+ 'resourceCenter.tab.network.hosts': 'Hosts',
+ 'resourceCenter.tab.network.tlsHosts': 'TLS Hosts',
+ 'resourceCenter.tab.network.backendServices': '后端服务',
'resourceCenter.tab.config.title': '配置',
'resourceCenter.tab.config.navDescription': 'ConfigMap 与 Secret 统一查看',
@@ -180,11 +186,11 @@ const resource = {
'resourceCenter.metrics.pod.errorHelper': '需要重点查看日志或事件',
'resourceCenter.metrics.network.total': '网络对象数',
- 'resourceCenter.metrics.network.totalHelper': '当前团队下的 Service 资源',
+ 'resourceCenter.metrics.network.totalHelper': '当前团队下的 Service 与 Ingress 资源',
'resourceCenter.metrics.network.ports': '暴露端口',
'resourceCenter.metrics.network.portsHelper': '已声明的端口与协议总数',
'resourceCenter.metrics.network.exposed': '外部暴露',
- 'resourceCenter.metrics.network.exposedHelper': '非 ClusterIP 类型的 Service',
+ 'resourceCenter.metrics.network.exposedHelper': '非 ClusterIP Service 与 Ingress 入口',
'resourceCenter.metrics.network.selectorless': '无选择器',
'resourceCenter.metrics.network.selectorlessHelper': '需要人工确认流量绑定对象',
@@ -219,6 +225,7 @@ const resource = {
'resourceCenter.yaml.toolbar.createHint': '支持 `.yaml` / `.yml`,导入后可继续校对与修改。',
'resourceCenter.yaml.toolbar.multiDoc': '支持多文档',
'resourceCenter.yaml.toolbar.manualEdit': '保留手动编辑',
+ 'resourceCenter.yaml.nativeResourceNotice': '这里创建的是 Kubernetes 原生资源,创建后在「K8S 原生资源」中查看和管理,不会自动出现在「应用管理」。如需应用管理中的应用或组件,请从应用管理入口创建。',
'resourceCenter.yaml.placeholder': 'apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: my-app\n...',
'resourceCenter.yaml.ok.create': '创建',
'resourceCenter.yaml.ok.edit': '保存',
@@ -305,6 +312,7 @@ const resource = {
'resourceCenter.helm.modal.tabExternalHelper': '支持官方、自建 Repo 与 OCI',
'resourceCenter.helm.modal.tabUpload': '上传 Chart 包',
'resourceCenter.helm.modal.tabUploadHelper': '上传 .tgz 后直接安装 Release',
+ 'resourceCenter.helm.modal.nativeResourceNotice': '这里安装的是 Helm Release,会作为 Kubernetes 原生资源在「K8S 原生资源」中查看和管理,不会自动出现在「应用管理」。如需应用管理中的应用或组件,请从应用管理入口创建。',
'resourceCenter.helm.modal.externalNotice': '请直接填写 Chart 地址,支持 Helm 官方或自建 Helm Repo 中的 Chart 包地址,以及使用 OCI 格式的制品仓库。点击下一步后会自动检测并解析 Chart。',
'resourceCenter.helm.modal.externalNoticeShort': '请直接填写 Chart 地址,支持 Helm Repo 包地址和 OCI 制品地址。',
'resourceCenter.helm.modal.externalSupport': '支持 Helm 官方或自建 Helm Repo 仓库,以及使用 OCI 格式的制品仓库。',
diff --git a/src/models/teamResources.js b/src/models/teamResources.js
index f1afa1634..9de51ec73 100644
--- a/src/models/teamResources.js
+++ b/src/models/teamResources.js
@@ -102,6 +102,14 @@ export default {
const resources = [...configMaps, ...secrets];
yield put({ type: 'save', payload: { resources, total: resources.length } });
},
+ *fetchNetworkResources({ payload }, { call, put }) {
+ const servicesRes = yield call(listNsResources, { ...payload, group: '', version: 'v1', resource: 'services' });
+ const ingressesRes = yield call(listNsResources, { ...payload, group: 'networking.k8s.io', version: 'v1', resource: 'ingresses' });
+ const services = (servicesRes && servicesRes.bean && servicesRes.bean.list) || [];
+ const ingresses = (ingressesRes && ingressesRes.bean && ingressesRes.bean.list) || [];
+ const resources = [...services, ...ingresses];
+ yield put({ type: 'save', payload: { resources, total: resources.length } });
+ },
*createResource({ payload, callback, handleError }, { call }) {
const res = yield call(createNsResource, { ...payload, handleError });
if (res && callback) callback(res);
diff --git a/src/pages/Extension/pluginCapacity/pluginTable.js b/src/pages/Extension/pluginCapacity/pluginTable.js
index 3d97e052f..6d7b769e7 100644
--- a/src/pages/Extension/pluginCapacity/pluginTable.js
+++ b/src/pages/Extension/pluginCapacity/pluginTable.js
@@ -189,19 +189,59 @@ class Index extends PureComponent {
loading: false
})
} else {
- this.setState({
- pluginList: [],
- loading: false
- })
+ this.handleInstalledPluginList(eid);
+ }
+ },
+ handleError: () => {
+ this.handleInstalledPluginList(eid);
+ }
+ })
+ }
+
+ normalizeInstalledPlugin = plugin => ({
+ ...plugin,
+ plugin_id: plugin.plugin_id || plugin.name,
+ plugin_name: plugin.plugin_name || plugin.alias || plugin.display_name || plugin.name,
+ description: plugin.description || '',
+ installed: true,
+ installed_version: plugin.installed_version || plugin.version || '',
+ can_upgrade: false,
+ upgradeable: false,
+ latest_version: plugin.latest_version || '',
+ author: plugin.author || 'Rainbond 官方',
+ app_level: plugin.app_level || plugin.appLevel || 'enterprise'
+ })
+
+ handleInstalledPluginList = (eid, callback) => {
+ const { dispatch, regionName } = this.props;
+ dispatch({
+ type: 'teamControl/fetchPluginUrl',
+ payload: {
+ enterprise_id: eid,
+ region_name: regionName,
+ },
+ callback: res => {
+ const plugins = res && res.list && res.list.length > 0
+ ? res.list.map(item => this.normalizeInstalledPlugin(item))
+ : [];
+ this.setState({
+ pluginList: plugins,
+ loading: false
+ });
+ if (callback) {
+ callback(plugins);
}
},
handleError: () => {
this.setState({
pluginList: [],
loading: false
- })
+ });
+ if (callback) {
+ callback([]);
+ }
}
- })
+ });
}
onJumpApp = (value, tab = 'upgrade') => {
@@ -454,6 +494,18 @@ class Index extends PureComponent {
});
}
+ completeInstallIfRunning = (pluginId, plugins) => {
+ const target = plugins.find(p => p.plugin_id === pluginId);
+ const status = target && `${target.status || ''}`.toUpperCase();
+ if (target && status === 'RUNNING') {
+ this.stopInstallPolling();
+ this.setState(prev => ({
+ installingPlugins: { ...prev.installingPlugins, [pluginId]: false },
+ installModalPhase: 'RUNNING',
+ }));
+ }
+ }
+
startInstallPolling = (pluginId) => {
this.stopInstallPolling();
this.installPollTimer = setInterval(() => {
@@ -470,14 +522,11 @@ class Index extends PureComponent {
callback: res => {
if (res && res.list && res.list.length > 0) {
this.setState({ pluginList: res.list });
- const target = res.list.find(p => p.plugin_id === pluginId);
- if (target && target.status === 'RUNNING') {
- this.stopInstallPolling();
- this.setState(prev => ({
- installingPlugins: { ...prev.installingPlugins, [pluginId]: false },
- installModalPhase: 'RUNNING',
- }));
- }
+ this.completeInstallIfRunning(pluginId, res.list);
+ } else {
+ this.handleInstalledPluginList(eid, plugins => {
+ this.completeInstallIfRunning(pluginId, plugins);
+ });
}
},
});
diff --git a/src/pages/Group/components/AppShareAppInfo.js b/src/pages/Group/components/AppShareAppInfo.js
index d72e561e5..75726c852 100644
--- a/src/pages/Group/components/AppShareAppInfo.js
+++ b/src/pages/Group/components/AppShareAppInfo.js
@@ -131,7 +131,7 @@ class AppShareAppInfo extends PureComponent {
const { getFieldDecorator, getFieldValue } = form;
const pd16 = { padding: 16 };
if (app.extend_method_map) {
- const steps = getFieldValue(`${ID}||step_node`);
+ const steps = getFieldValue(`extend||step_node||${ID}`);
return (
@@ -152,7 +152,7 @@ class AppShareAppInfo extends PureComponent {
})}
style={pd16}
>
- {getFieldDecorator(`${ID}||min_node`, {
+ {getFieldDecorator(`extend||min_node||${ID}`, {
initialValue: app.extend_method_map.min_node,
rules: [
{
@@ -181,7 +181,7 @@ class AppShareAppInfo extends PureComponent {
})}
style={pd16}
>
- {getFieldDecorator(`${ID}||max_node`, {
+ {getFieldDecorator(`extend||max_node||${ID}`, {
initialValue: app.extend_method_map.max_node,
rules: [
{
@@ -210,7 +210,7 @@ class AppShareAppInfo extends PureComponent {
})}
style={pd16}
>
- {getFieldDecorator(`${ID}||step_node`, {
+ {getFieldDecorator(`extend||step_node||${ID}`, {
initialValue: app.extend_method_map.step_node,
rules: [
{
@@ -239,7 +239,7 @@ class AppShareAppInfo extends PureComponent {
})}
style={pd16}
>
- {getFieldDecorator(`${ID}||init_memory`, {
+ {getFieldDecorator(`extend||init_memory||${ID}`, {
initialValue: app.extend_method_map.init_memory || 0,
rules: [
{
@@ -272,7 +272,7 @@ class AppShareAppInfo extends PureComponent {
})}
style={pd16}
>
- {getFieldDecorator(`${ID}||container_cpu`, {
+ {getFieldDecorator(`extend||container_cpu||${ID}`, {
initialValue: app.extend_method_map.container_cpu || 0,
rules: [
{
diff --git a/src/pages/Group/components/appShareFormHelpers.js b/src/pages/Group/components/appShareFormHelpers.js
new file mode 100644
index 000000000..d247aa0c9
--- /dev/null
+++ b/src/pages/Group/components/appShareFormHelpers.js
@@ -0,0 +1,108 @@
+const cloneShareService = item => ({
+ ...item,
+ extend_method_map: item.extend_method_map
+ ? { ...item.extend_method_map }
+ : item.extend_method_map,
+ dep_service_map_list: Array.isArray(item.dep_service_map_list)
+ ? item.dep_service_map_list.map(dep => ({ ...dep }))
+ : [],
+ service_connect_info_map_list: Array.isArray(item.service_connect_info_map_list)
+ ? item.service_connect_info_map_list.map(config => ({ ...config }))
+ : [],
+ service_env_map_list: Array.isArray(item.service_env_map_list)
+ ? item.service_env_map_list.map(config => ({ ...config }))
+ : []
+});
+
+const collectShareServiceData = ({
+ shareServiceList = [],
+ selectedShareKeys = [],
+ componentRefs = []
+}) => {
+ const shareServiceData = shareServiceList.map(cloneShareService);
+ let componentFormHasError = false;
+
+ componentRefs.forEach(app => {
+ if (!app || !app.props || !app.props.form) {
+ return;
+ }
+ const apptab = app.props.tab;
+ let componentValues = null;
+ app.props.form.validateFields((errs, val) => {
+ if (errs) {
+ componentFormHasError = true;
+ return;
+ }
+ componentValues = val;
+ });
+ if (componentFormHasError || !componentValues) {
+ return;
+ }
+ shareServiceData.forEach(option => {
+ const ID = option.service_id;
+ if (option.service_alias !== apptab) {
+ return;
+ }
+ Object.keys(componentValues).forEach(index => {
+ const indexarr = index.split('||');
+ const firstInfo = indexarr && indexarr.length > 0 && indexarr[0];
+ if (!firstInfo) {
+ return;
+ }
+ const isConnect = firstInfo === 'connect';
+ const isEnv = firstInfo === 'env';
+
+ if (isConnect && indexarr[2] !== 'random') {
+ option.service_connect_info_map_list.forEach(serapp => {
+ if (
+ serapp.attr_name === indexarr[1] &&
+ String(ID) === String(indexarr[3])
+ ) {
+ serapp[indexarr[2]] = componentValues[index];
+ serapp.is_change = true;
+ }
+ });
+ }
+
+ if (isEnv) {
+ option.service_env_map_list.forEach(serapp => {
+ if (
+ serapp.attr_name === indexarr[1] &&
+ String(ID) === String(indexarr[2])
+ ) {
+ serapp.attr_value = componentValues[index];
+ serapp.is_change = true;
+ }
+ });
+ }
+
+ if (
+ firstInfo === 'extend' &&
+ option.extend_method_map &&
+ String(ID) === String(indexarr[2])
+ ) {
+ option.extend_method_map[indexarr[1]] = componentValues[index];
+ }
+ });
+ });
+ });
+
+ const selectedShareServices = [];
+ selectedShareKeys.forEach(shareKey => {
+ shareServiceData.forEach(option => {
+ if (shareKey === option.service_share_uuid) {
+ selectedShareServices.push(option);
+ }
+ });
+ });
+
+ return {
+ componentFormHasError,
+ shareServiceData,
+ selectedShareServices
+ };
+};
+
+module.exports = {
+ collectShareServiceData
+};
diff --git a/src/pages/Group/components/appShareFormHelpers.node.test.js b/src/pages/Group/components/appShareFormHelpers.node.test.js
new file mode 100644
index 000000000..445dc165c
--- /dev/null
+++ b/src/pages/Group/components/appShareFormHelpers.node.test.js
@@ -0,0 +1,47 @@
+const assert = require('assert');
+const { collectShareServiceData } = require('./appShareFormHelpers');
+
+const componentRef = {
+ props: {
+ tab: 'web',
+ form: {
+ validateFields(callback) {
+ callback(null, {
+ 'extend||min_node||service-1': 2,
+ 'extend||max_node||service-1': 5,
+ 'extend||step_node||service-1': 2,
+ 'extend||init_memory||service-1': 1024,
+ 'extend||container_cpu||service-1': 500
+ });
+ }
+ }
+ }
+};
+
+const result = collectShareServiceData({
+ shareServiceList: [
+ {
+ service_id: 'service-1',
+ service_alias: 'web',
+ service_share_uuid: 'service-1+service-1',
+ extend_method_map: {
+ min_node: 1,
+ max_node: 3,
+ step_node: 1,
+ init_memory: 512,
+ container_cpu: 250
+ }
+ }
+ ],
+ selectedShareKeys: ['service-1+service-1'],
+ componentRefs: [componentRef]
+});
+
+assert.strictEqual(result.componentFormHasError, false);
+assert.deepStrictEqual(result.selectedShareServices[0].extend_method_map, {
+ min_node: 2,
+ max_node: 5,
+ step_node: 2,
+ init_memory: 1024,
+ container_cpu: 500
+});
diff --git a/src/pages/Group/components/appShareHelpers.js b/src/pages/Group/components/appShareHelpers.js
index f9cfbaa34..5f24f3dc4 100644
--- a/src/pages/Group/components/appShareHelpers.js
+++ b/src/pages/Group/components/appShareHelpers.js
@@ -5,8 +5,13 @@ const {
DEFAULT_SNAPSHOT_VERSION,
buildNextSnapshotVersion
} = snapshotVersionHelpers;
+const { collectShareServiceData } = require('./appShareFormHelpers');
-export { DEFAULT_SNAPSHOT_VERSION, buildNextSnapshotVersion };
+export {
+ DEFAULT_SNAPSHOT_VERSION,
+ buildNextSnapshotVersion,
+ collectShareServiceData
+};
export const appShareStateSelector = ({
user,
@@ -33,104 +38,3 @@ export const validateShareVersion = value => {
}
return null;
};
-
-const cloneShareService = item => ({
- ...item,
- extend_method_map: item.extend_method_map
- ? { ...item.extend_method_map }
- : item.extend_method_map,
- dep_service_map_list: Array.isArray(item.dep_service_map_list)
- ? item.dep_service_map_list.map(dep => ({ ...dep }))
- : [],
- service_connect_info_map_list: Array.isArray(item.service_connect_info_map_list)
- ? item.service_connect_info_map_list.map(config => ({ ...config }))
- : [],
- service_env_map_list: Array.isArray(item.service_env_map_list)
- ? item.service_env_map_list.map(config => ({ ...config }))
- : []
-});
-
-export const collectShareServiceData = ({
- shareServiceList = [],
- selectedShareKeys = [],
- componentRefs = []
-}) => {
- const shareServiceData = shareServiceList.map(cloneShareService);
- let componentFormHasError = false;
-
- componentRefs.forEach(app => {
- if (!app || !app.props || !app.props.form) {
- return;
- }
- const apptab = app.props.tab;
- let componentValues = null;
- app.props.form.validateFields((errs, val) => {
- if (errs) {
- componentFormHasError = true;
- return;
- }
- componentValues = val;
- });
- if (componentFormHasError || !componentValues) {
- return;
- }
- shareServiceData.forEach(option => {
- const ID = option.service_id;
- if (option.service_alias !== apptab) {
- return;
- }
- Object.keys(componentValues).forEach(index => {
- const indexarr = index.split('||');
- const firstInfo = indexarr && indexarr.length > 0 && indexarr[0];
- if (!firstInfo) {
- return;
- }
- const isConnect = firstInfo === 'connect';
- const isEnv = firstInfo === 'env';
-
- if (isConnect && indexarr[2] !== 'random') {
- option.service_connect_info_map_list.forEach(serapp => {
- if (
- serapp.attr_name === indexarr[1] &&
- String(ID) === String(indexarr[3])
- ) {
- serapp[indexarr[2]] = componentValues[index];
- serapp.is_change = true;
- }
- });
- }
-
- if (isEnv) {
- option.service_env_map_list.forEach(serapp => {
- if (
- serapp.attr_name === indexarr[1] &&
- String(ID) === String(indexarr[2])
- ) {
- serapp.attr_value = componentValues[index];
- serapp.is_change = true;
- }
- });
- }
-
- if (firstInfo === 'extend' && option.extend_method_map) {
- option.extend_method_map[indexarr[1]] = componentValues[index];
- }
- });
- });
- });
-
- const selectedShareServices = [];
- selectedShareKeys.forEach(shareKey => {
- shareServiceData.forEach(option => {
- if (shareKey === option.service_share_uuid) {
- selectedShareServices.push(option);
- }
- });
- });
-
- return {
- componentFormHasError,
- shareServiceData,
- selectedShareServices
- };
-};
diff --git a/src/pages/ResourceCenter/components/HelmModals.js b/src/pages/ResourceCenter/components/HelmModals.js
index 1f4770699..d2dcf4fad 100644
--- a/src/pages/ResourceCenter/components/HelmModals.js
+++ b/src/pages/ResourceCenter/components/HelmModals.js
@@ -69,6 +69,18 @@ class HelmModals extends PureComponent {
);
}
+ renderHelmNativeResourceNotice() {
+ const helmModalMode = this.getStateValue('helmModalMode', 'install');
+ if (helmModalMode !== 'install') {
+ return null;
+ }
+ return (
+
+ {t('resourceCenter.helm.modal.nativeResourceNotice', '这里安装的是 Helm Release,会作为 Kubernetes 原生资源在「K8S 原生资源」中查看和管理,不会自动出现在「应用管理」。如需应用管理中的应用或组件,请从应用管理入口创建。')}
+
+ );
+ }
+
renderHelmBrowse() {
const helmRepos = this.getStateValue('helmRepos', []);
const helmRepoLoading = this.getStateValue('helmRepoLoading', false);
@@ -785,6 +797,7 @@ class HelmModals extends PureComponent {
bodyStyle={{ padding: '16px 24px', minHeight: 560 }}
>
{this.renderHelmUpgradeAssistant()}
+ {this.renderHelmNativeResourceNotice()}
{helmStep === 'source' && this.renderHelmSourceTabs()}
{this.renderHelmStepContent()}
diff --git a/src/pages/ResourceCenter/index.js b/src/pages/ResourceCenter/index.js
index 69f5f5f87..6a714cd2f 100644
--- a/src/pages/ResourceCenter/index.js
+++ b/src/pages/ResourceCenter/index.js
@@ -45,7 +45,7 @@ import HelmTab from './tabs/HelmTab';
currentEnterprise: enterprise.currentEnterprise,
rainbondInfo: global.rainbondInfo,
currentUser: user.currentUser,
- resourceListLoading: loading.effects['teamResources/fetchResources'],
+ resourceListLoading: loading.effects['teamResources/fetchResources'] || loading.effects['teamResources/fetchNetworkResources'],
configListLoading: loading.effects['teamResources/fetchConfigResources'],
helmListLoading: loading.effects['teamResources/fetchHelmReleases'],
resourceYamlLoading: loading.effects['teamResources/fetchResource'],
@@ -389,6 +389,13 @@ class ResourceCenter extends PureComponent {
});
return;
}
+ if (tab === 'network') {
+ dispatch({
+ type: 'teamResources/fetchNetworkResources',
+ payload: { team: teamName, region: regionName },
+ });
+ return;
+ }
const resourceParams = tab === 'workload'
? { group: extra.group || workloadKindGroup || 'apps', version: 'v1', resource: extra.resource || workloadKind || 'deployments' }
: TAB_RESOURCE_MAP[tab] || TAB_RESOURCE_MAP.workload;
@@ -427,6 +434,13 @@ class ResourceCenter extends PureComponent {
const kind = ((record && record.kind) || '').toLowerCase();
return { group: '', version: 'v1', resource: kind === 'secret' ? 'secrets' : 'configmaps' };
}
+ if (tab === 'network') {
+ const kind = ((record && record.kind) || '').toLowerCase();
+ if (kind === 'ingress') {
+ return { group: 'networking.k8s.io', version: 'v1', resource: 'ingresses' };
+ }
+ return { group: '', version: 'v1', resource: 'services' };
+ }
return this.getCurrentResourceParams(tab);
};
@@ -666,9 +680,11 @@ class ResourceCenter extends PureComponent {
}
if (activeTab === 'network') {
- const portCount = list.reduce((total, item) => total + ((item.ports || []).length || 0), 0);
- const exposedCount = list.filter(item => item.type && item.type !== 'ClusterIP').length;
- const selectorlessCount = list.filter(item => !item.selector || Object.keys(item.selector).length === 0).length;
+ const serviceList = list.filter(item => ((item.kind || 'Service').toLowerCase() === 'service'));
+ const ingressCount = list.filter(item => ((item.kind || '').toLowerCase() === 'ingress')).length;
+ const portCount = serviceList.reduce((total, item) => total + ((item.ports || []).length || 0), 0);
+ const exposedCount = serviceList.filter(item => item.type && item.type !== 'ClusterIP').length + ingressCount;
+ const selectorlessCount = serviceList.filter(item => !item.selector || Object.keys(item.selector).length === 0).length;
return [
{ label: formatMessage({ id: 'resourceCenter.metrics.network.total' }), value: list.length, helper: formatMessage({ id: 'resourceCenter.metrics.network.totalHelper' }), tone: 'default' },
{ label: formatMessage({ id: 'resourceCenter.metrics.network.ports' }), value: portCount, helper: formatMessage({ id: 'resourceCenter.metrics.network.portsHelper' }), tone: 'running' },
@@ -842,6 +858,15 @@ class ResourceCenter extends PureComponent {
}));
};
+ handleNetworkDetail = (record) => {
+ const kind = ((record && record.kind) || '').toLowerCase();
+ if (kind === 'ingress') {
+ this.handleOpenResourceYaml(record, this.getRecordResourceParams(record, 'network'));
+ return;
+ }
+ this.jumpToServiceDetail(record);
+ };
+
jumpToHelmDetail = (record, query = {}) => {
const { dispatch } = this.props;
const { teamName, regionName } = this.getParams();
@@ -2100,8 +2125,8 @@ class ResourceCenter extends PureComponent {
onRefresh={() => this.fetchTabData(activeTab)}
refreshLoading={tabLoading}
onCreate={this.openCreateChooser}
- onDetail={this.jumpToServiceDetail}
- onEditYaml={record => this.handleOpenResourceYaml(record, { group: '', version: 'v1', resource: 'services' })}
+ onDetail={this.handleNetworkDetail}
+ onEditYaml={record => this.handleOpenResourceYaml(record, this.getRecordResourceParams(record, 'network'))}
onDelete={this.handleDeleteResource}
deletingName={deletingResourceName}
yamlLoadingName={openingYamlName}
@@ -2235,6 +2260,14 @@ class ResourceCenter extends PureComponent {
) : null}
+ {yamlModalMode === 'create' && !yamlResultStep ? (
+
+ {formatMessage({
+ id: 'resourceCenter.yaml.nativeResourceNotice',
+ defaultMessage: '这里创建的是 Kubernetes 原生资源,创建后在「K8S 原生资源」中查看和管理,不会自动出现在「应用管理」。如需应用管理中的应用或组件,请从应用管理入口创建。',
+ })}
+
+ ) : null}
{yamlResultStep ? this.renderYamlResultPanel() : (
((record && record.kind) || '').toLowerCase() === 'ingress';
+
+const renderTagList = (values, className) => {
+ const list = (Array.isArray(values) ? values : []).filter(Boolean);
+ if (list.length === 0) {
+ return -;
+ }
+ return list.map(value => (
+ {value}
+ ));
+};
+
+const renderServicePorts = (record) => {
+ const ports = Array.isArray(record.ports) ? record.ports : [];
+ if (ports.length === 0) {
+ return -;
+ }
+ return ports.map((port, index) => (
+ {port.port}/{port.protocol || 'TCP'}
+ ));
+};
+
+const renderServiceSelector = (record) => {
+ return record.selector
+ ? Object.entries(record.selector).map(([key, selectorValue]) => (
+ {key}={selectorValue}
+ ))
+ : -;
+};
class NetworkTab extends PureComponent {
render() {
@@ -26,7 +59,30 @@ class NetworkTab extends PureComponent {
emptyContent,
} = this.props;
- const columns = [
+ const serviceData = (data || []).filter(record => !isIngressResource(record));
+ const ingressData = (data || []).filter(isIngressResource);
+
+ const renderActions = record => (
+
+ onEditYaml(record)}>
+ {formatMessage({ id: 'resourceCenter.common.edit' })}
+
+
+ {deletingName === record.name ? (
+
+ {formatMessage({ id: 'resourceCenter.common.delete' })}
+
+ ) : (
+ onDelete(record)}>
+
+ {formatMessage({ id: 'resourceCenter.common.delete' })}
+
+
+ )}
+
+ );
+
+ const serviceColumns = [
{
title: formatMessage({ id: 'resourceCenter.common.name' }),
dataIndex: 'name',
@@ -43,22 +99,14 @@ class NetworkTab extends PureComponent {
{ title: formatMessage({ id: 'resourceCenter.detail.clusterIp', defaultMessage: 'Cluster IP' }), dataIndex: 'cluster_ip', key: 'cluster_ip', width: 150, render: value => {value || '-'} },
{
title: formatMessage({ id: 'resourceCenter.common.ports' }),
- dataIndex: 'ports',
key: 'ports',
width: 220,
- render: ports => (Array.isArray(ports) ? ports : []).map((port, index) => (
- {port.port}/{port.protocol || 'TCP'}
- )),
+ render: (_, record) => renderServicePorts(record),
},
{
title: formatMessage({ id: 'resourceCenter.common.selector' }),
- dataIndex: 'selector',
key: 'selector',
- render: value => value
- ? Object.entries(value).map(([key, selectorValue]) => (
- {key}={selectorValue}
- ))
- : -,
+ render: (_, record) => renderServiceSelector(record),
},
{
title: formatMessage({ id: 'resourceCenter.common.createdAt' }),
@@ -72,26 +120,57 @@ class NetworkTab extends PureComponent {
key: 'action',
width: 140,
fixed: 'right',
- render: (_, record) => (
-
- onEditYaml(record)}>
- {formatMessage({ id: 'resourceCenter.common.edit' })}
-
-
- {deletingName === record.name ? (
-
- {formatMessage({ id: 'resourceCenter.common.delete' })}
-
- ) : (
- onDelete(record)}>
-
- {formatMessage({ id: 'resourceCenter.common.delete' })}
-
-
- )}
+ render: (_, record) => renderActions(record),
+ },
+ ];
+
+ const ingressColumns = [
+ {
+ title: formatMessage({ id: 'resourceCenter.common.name' }),
+ dataIndex: 'name',
+ key: 'name',
+ width: 220,
+ fixed: 'left',
+ render: (text, record) => (
+ onDetail(record)}>
+ {text}
),
},
+ { title: formatMessage({ id: 'resourceCenter.tab.network.ingressClass', defaultMessage: 'IngressClass' }), dataIndex: 'ingress_class', key: 'ingress_class', width: 160, render: value => value ? {value} : - },
+ {
+ title: formatMessage({ id: 'resourceCenter.tab.network.hosts', defaultMessage: 'Hosts' }),
+ dataIndex: 'hosts',
+ key: 'hosts',
+ render: value => renderTagList(value, styles.tagInfo),
+ },
+ {
+ title: formatMessage({ id: 'resourceCenter.tab.network.tlsHosts', defaultMessage: 'TLS Hosts' }),
+ dataIndex: 'tls_hosts',
+ key: 'tls_hosts',
+ render: value => renderTagList(value, styles.tagInfo),
+ },
+ {
+ title: formatMessage({ id: 'resourceCenter.tab.network.backendServices', defaultMessage: 'Backend Services' }),
+ dataIndex: 'backend_services',
+ key: 'backend_services',
+ width: 220,
+ render: value => renderTagList(value, styles.tagPrimary),
+ },
+ {
+ title: formatMessage({ id: 'resourceCenter.common.createdAt' }),
+ dataIndex: 'created_at',
+ key: 'created_at',
+ width: 180,
+ render: value => {formatBrowserLocalTime(value)},
+ },
+ {
+ title: formatMessage({ id: 'resourceCenter.common.operation' }),
+ key: 'action',
+ width: 140,
+ fixed: 'right',
+ render: (_, record) => renderActions(record),
+ },
];
return (
@@ -105,17 +184,34 @@ class NetworkTab extends PureComponent {
primaryActionLabel={formatMessage({ id: 'resourceCenter.common.createResource' })}
onPrimaryAction={onCreate}
/>
-
+
+
+ `Service-${record.name}`}
+ size="middle"
+ loading={refreshLoading}
+ scroll={getTableScroll(SERVICE_TABLE_SCROLL_X)}
+ pagination={getTablePagination(serviceData)}
+ locale={{ emptyText: emptyContent }}
+ />
+
+
+ `Ingress-${record.name}`}
+ size="middle"
+ loading={refreshLoading}
+ scroll={getTableScroll(INGRESS_TABLE_SCROLL_X)}
+ pagination={getTablePagination(ingressData)}
+ locale={{ emptyText: emptyContent }}
+ />
+
+
);
}