diff --git a/src/layouts/admin-menu.jsx b/src/layouts/admin-menu.jsx
index 5d271977..44926208 100644
--- a/src/layouts/admin-menu.jsx
+++ b/src/layouts/admin-menu.jsx
@@ -293,6 +293,13 @@ const renderMenu = (t) => {
level: 2,
routePath: '/network/networks-admin/detail/:id',
},
+ {
+ path: /^\/network\/networks-admin\/detail\/.[^/]+\/subnet\/.[^/]+$/,
+ name: t('Subnet Detail'),
+ key: 'subnetDetailAdmin',
+ level: 2,
+ routePath: '/network/networks-admin/detail/:networkId/subnet/:id',
+ },
],
},
{
@@ -308,6 +315,28 @@ const renderMenu = (t) => {
level: 2,
routePath: '/network/port-admin/detail/:id',
},
+ {
+ path: /^\/network\/networks-admin\/detail\/.[^/]+\/port\/.[^/]+$/,
+ name: t('Port Detail'),
+ key: 'networkPortDetailAdmin',
+ level: 2,
+ routePath: '/network/networks-admin/detail/:networkId/port/:id',
+ },
+ {
+ path: /^\/network\/networks-admin\/detail\/.[^/]+\/subnet\/.[^/]+\/port\/.[^/]+$/,
+ name: t('Port Detail'),
+ key: 'subnetPortDetailAdmin',
+ level: 2,
+ routePath:
+ '/network/networks-admin/detail/:networkId/subnet/:subnetId/port/:id',
+ },
+ {
+ path: /^\/network\/instance-admin\/detail\/.[^/]+\/port\/.[^/]+$/,
+ name: t('Port Detail'),
+ key: 'instancePortDetailAdmin',
+ level: 2,
+ routePath: '/network/instance-admin/detail/:instanceId/port/:id',
+ },
],
},
{
diff --git a/src/layouts/menu.jsx b/src/layouts/menu.jsx
index 91ad82c2..5c6e8cdb 100644
--- a/src/layouts/menu.jsx
+++ b/src/layouts/menu.jsx
@@ -259,6 +259,13 @@ const renderMenu = (t) => {
level: 2,
routePath: '/network/networks/detail/:id',
},
+ {
+ path: /^\/network\/networks\/detail\/.[^/]+\/subnet\/.[^/]+$/,
+ name: t('Subnet Detail'),
+ key: 'subnetDetail',
+ level: 2,
+ routePath: '/network/networks/detail/:networkId/subnet/:id',
+ },
],
},
{
@@ -274,6 +281,28 @@ const renderMenu = (t) => {
level: 2,
routePath: '/network/port/detail/:id',
},
+ {
+ path: /^\/network\/networks\/detail\/.[^/]+\/port\/.[^/]+$/,
+ name: t('Port Detail'),
+ key: 'networkPortDetail',
+ level: 2,
+ routePath: '/network/networks/detail/:networkId/port/:id',
+ },
+ {
+ path: /^\/network\/networks\/detail\/.[^/]+\/subnet\/.[^/]+\/port\/.[^/]+$/,
+ name: t('Port Detail'),
+ key: 'subnetPortDetail',
+ level: 2,
+ routePath:
+ '/network/networks/detail/:networkId/subnet/:subnetId/port/:id',
+ },
+ {
+ path: /^\/network\/instance\/detail\/.[^/]+\/port\/.[^/]+$/,
+ name: t('Port Detail'),
+ key: 'instancePortDetail',
+ level: 2,
+ routePath: '/network/instance/detail/:instanceId/port/:id',
+ },
],
},
{
diff --git a/src/locales/en.json b/src/locales/en.json
index ad7cdb30..5a1990fd 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -587,6 +587,7 @@
"DNS": "DNS",
"DNS Assignment": "DNS Assignment",
"DNS Name": "DNS Name",
+ "DNS Nameservers": "DNS Nameservers",
"DPD Action": "DPD Action",
"DPD Interval (sec)": "DPD Interval (sec)",
"DPD actions controls the use of Dead Peer Detection Protocol.": "DPD actions controls the use of Dead Peer Detection Protocol.",
@@ -841,6 +842,7 @@
"Enable Admin State": "Enable Admin State",
"Enable Compute Host": "Enable Compute Host",
"Enable Compute Service": "Enable Compute Service",
+ "Enable DHCP": "Enable DHCP",
"Enable Domain": "Enable Domain",
"Enable Floating IP": "Enable Floating IP",
"Enable HealthMonitor": "Enable HealthMonitor",
@@ -1089,6 +1091,7 @@
"IP Address": "IP Address",
"IP Distribution Mode": "IP Distribution Mode",
"IP Protocol": "IP Protocol",
+ "IP Usage": "IP Usage",
"IP Version": "IP Version",
"IP address allocation polls, one enter per line(e.g. 192.168.1.2,192.168.1.200)": "IP address allocation polls, one enter per line(e.g. 192.168.1.2,192.168.1.200)",
"IP address allocation polls, one enter per line(e.g. {ip})": "IP address allocation polls, one enter per line(e.g. {ip})",
@@ -1781,6 +1784,7 @@
"Pool Protocol": "Pool Protocol",
"Pools": "Pools",
"Port": "Port",
+ "Port Count": "Port Count",
"Port Detail": "Port Detail",
"Port Forwarding": "Port Forwarding",
"Port Forwardings": "Port Forwardings",
@@ -2217,6 +2221,7 @@
"Sub Users": "Sub Users",
"Subnet": "Subnet",
"Subnet Count": "Subnet Count",
+ "Subnet Detail": "Subnet Detail",
"Subnet ID": "Subnet ID",
"Subnet Name": "Subnet Name",
"Subnets": "Subnets",
@@ -2858,6 +2863,7 @@
"static routers": "static routers",
"stop instance": "stop instance",
"storage backend": "storage backend",
+ "subnet": "subnet",
"subnets": "subnets",
"suspend instance": "suspend instance",
"the Republic of Abkhazia": "the Republic of Abkhazia",
diff --git a/src/locales/zh.json b/src/locales/zh.json
index 0e03e6c7..1e2d7f33 100644
--- a/src/locales/zh.json
+++ b/src/locales/zh.json
@@ -587,6 +587,7 @@
"DNS": "DNS",
"DNS Assignment": "DNS指派",
"DNS Name": "DNS名称",
+ "DNS Nameservers": "DNS服务器",
"DPD Action": "DPD动作",
"DPD Interval (sec)": "DPD最大延迟(秒)",
"DPD actions controls the use of Dead Peer Detection Protocol.": "DPD动作控制对失效对端协议的处理方式。",
@@ -841,6 +842,7 @@
"Enable Admin State": "启用管理状态",
"Enable Compute Host": "启用计算节点",
"Enable Compute Service": "启用计算服务",
+ "Enable DHCP": "DHCP 已启用",
"Enable Domain": "启用域",
"Enable Floating IP": "使用浮动IP",
"Enable HealthMonitor": "启用健康检查",
@@ -1089,6 +1091,7 @@
"IP Address": "IP地址",
"IP Distribution Mode": "IP分配模式",
"IP Protocol": "IP协议",
+ "IP Usage": "IP使用情况",
"IP Version": "IP版本",
"IP address allocation polls, one enter per line(e.g. 192.168.1.2,192.168.1.200)": "IP地址分配池,每行一条(例如: 192.168.1.2,192.168.1.200)",
"IP address allocation polls, one enter per line(e.g. {ip})": "IP地址分配池,每行一条(例如: {ip})",
@@ -1781,6 +1784,7 @@
"Pool Protocol": "资源池协议",
"Pools": "",
"Port": "端口",
+ "Port Count": "端口数量",
"Port Detail": "端口详情",
"Port Forwarding": "端口转发",
"Port Forwardings": "端口转发",
@@ -2217,6 +2221,7 @@
"Sub Users": "组内用户列表",
"Subnet": "子网",
"Subnet Count": "子网数量",
+ "Subnet Detail": "子网详情",
"Subnet ID": "子网ID",
"Subnet Name": "子网名称",
"Subnets": "子网",
@@ -2858,6 +2863,7 @@
"static routers": "静态路由",
"stop instance": "关闭云主机",
"storage backend": "存储后端",
+ "subnet": "子网",
"subnets": "子网",
"suspend instance": "挂起云主机",
"the Republic of Abkhazia": "阿布哈兹",
diff --git a/src/pages/network/containers/Network/Detail/index.jsx b/src/pages/network/containers/Network/Detail/index.jsx
index ed0e1dbe..3961b04b 100644
--- a/src/pages/network/containers/Network/Detail/index.jsx
+++ b/src/pages/network/containers/Network/Detail/index.jsx
@@ -16,9 +16,9 @@ import { inject, observer } from 'mobx-react';
import Base from 'containers/TabDetail';
import { NetworkStore } from 'stores/neutron/network';
import { networkStatus } from 'resources/neutron/network';
-import Port from 'src/pages/network/containers/Port';
+import Port from 'pages/network/containers/Port';
import globalRootStore from 'stores/root';
-import Subnets from './Subnets';
+import Subnet from 'pages/network/containers/Subnet';
import Detail from './Detail';
import actionConfigs from '../actions';
@@ -140,7 +140,7 @@ export class NetworkDetail extends Base {
{
title: t('Subnets'),
key: 'subnets',
- component: Subnets,
+ component: Subnet,
},
{
title: t('Ports'),
diff --git a/src/pages/network/containers/Port/Detail/index.jsx b/src/pages/network/containers/Port/Detail/index.jsx
index 53dd0c48..da902583 100644
--- a/src/pages/network/containers/Port/Detail/index.jsx
+++ b/src/pages/network/containers/Port/Detail/index.jsx
@@ -32,9 +32,43 @@ export class PortDetail extends Base {
}
get listUrl() {
+ const { networkId, subnetId, instanceId } = this.params;
+ if (this.isSubnetPortDetail) {
+ return this.getRoutePath(
+ 'subnetDetail',
+ { id: subnetId, networkId },
+ { tab: 'ports' }
+ );
+ }
+ if (this.isNetworkPortDetail) {
+ return this.getRoutePath(
+ 'networkDetail',
+ { id: networkId },
+ { tab: 'ports' }
+ );
+ }
+ if (this.isInstancePortDetail) {
+ return this.getRoutePath(
+ 'instanceDetail',
+ { id: instanceId },
+ { tab: 'interface' }
+ );
+ }
return this.getRoutePath('port');
}
+ get isSubnetPortDetail() {
+ return this.path.includes('subnet');
+ }
+
+ get isNetworkPortDetail() {
+ return this.path.includes('networks') && !this.isSubnetPortDetail;
+ }
+
+ get isInstancePortDetail() {
+ return this.path.includes('instance');
+ }
+
get actionConfigs() {
if (this.isAdminPage) {
return actionConfigs.adminActions;
diff --git a/src/pages/network/containers/Port/index.jsx b/src/pages/network/containers/Port/index.jsx
index 858314e8..8c2faa29 100644
--- a/src/pages/network/containers/Port/index.jsx
+++ b/src/pages/network/containers/Port/index.jsx
@@ -38,20 +38,29 @@ export class Port extends Base {
return (
this.inDetailPage &&
(this.path.includes('networks/detail') ||
- this.path.includes('networks-admin/detail'))
+ this.path.includes('networks-admin/detail')) &&
+ !this.isSubnetDetail
);
}
+ get isSubnetDetail() {
+ return this.inDetailPage && this.path.includes('subnet');
+ }
+
+ get isRecycleBinDetail() {
+ return this.inDetailPage && this.path.includes('recycle-bin');
+ }
+
get isFilterByBackend() {
- return true;
+ return !this.isSubnetDetail;
}
get isSortByBackend() {
- return true;
+ return this.isFilterByBackend;
}
get defaultSortKey() {
- return 'status';
+ return this.isFilterByBackend ? 'status' : '';
}
updateFetchParamsByPage = (params) => {
@@ -65,6 +74,15 @@ export class Port extends Base {
return newParams;
};
+ updateFetchParams = (params) => {
+ const { id, networkId, ...rest } = params;
+ return {
+ network_id: networkId,
+ subnetId: id,
+ ...rest,
+ };
+ };
+
get policy() {
return 'get_port';
}
@@ -77,10 +95,6 @@ export class Port extends Base {
return true;
}
- get isRecycleBinDetail() {
- return this.inDetailPage && this.path.includes('recycle-bin');
- }
-
get actionConfigs() {
if (this.isRecycleBinDetail) {
return emptyActionConfig;
@@ -141,12 +155,44 @@ export class Port extends Base {
);
};
+ getPortDetailRoute = () => {
+ if (this.isSubnetDetail) {
+ return {
+ routeName: this.getRouteName('subnetPortDetail'),
+ routeParamsFunc: (data) => ({
+ networkId: data.network_id,
+ subnetId: data.subnet_id,
+ id: data.id,
+ }),
+ };
+ }
+ if (this.isNetworkDetail) {
+ return {
+ routeName: this.getRouteName('networkPortDetail'),
+ routeParamsFunc: (data) => ({
+ networkId: data.network_id,
+ id: data.id,
+ }),
+ };
+ }
+ if (this.isInstanceDetail) {
+ return {
+ routeName: this.getRouteName('instancePortDetail'),
+ routeParamsFunc: (data) => ({
+ instanceId: data.device_id,
+ id: data.id,
+ }),
+ };
+ }
+ return { routeName: this.getRouteName('portDetail') };
+ };
+
getColumns = () => {
const columns = [
{
title: t('ID/Name'),
dataIndex: 'name',
- routeName: this.getRouteName('portDetail'),
+ ...this.getPortDetailRoute(),
},
{
title: t('Project ID/Name'),
@@ -261,7 +307,7 @@ export class Port extends Base {
{ label: t('DHCP Agent'), key: 'network:dhcp' },
{
label: t('Others'),
- key: 'network:local_ip,network:routed,network:distributed',
+ key: 'network:local_ip,network:routed,network:distributed,compute:kuryr',
},
{
label: t('Unbounded'),
@@ -269,7 +315,18 @@ export class Port extends Base {
},
],
};
- ret.push(deviceOwner);
+ if (this.isSubnetDetail) {
+ deviceOwner.filterFunc = (value, filter) => {
+ if (filter === 'none') {
+ return !value;
+ }
+ return value && filter.includes(value);
+ };
+ }
+ if (!this.isInstanceDetail) {
+ ret.push(deviceOwner);
+ }
+
return ret;
}
}
diff --git a/src/pages/network/containers/Subnet/Detail/Detail.jsx b/src/pages/network/containers/Subnet/Detail/Detail.jsx
new file mode 100644
index 00000000..b64cb88b
--- /dev/null
+++ b/src/pages/network/containers/Subnet/Detail/Detail.jsx
@@ -0,0 +1,140 @@
+// Copyright 2022 99cloud
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import React from 'react';
+import { inject, observer } from 'mobx-react';
+import Base from 'containers/BaseDetail';
+
+export class BaseDetail extends Base {
+ get leftCards() {
+ const cards = [this.networkCard, this.baseInfoCard];
+ if (this.canAddNetworkIPUsageInfo) {
+ cards.push(this.ipUsageCard);
+ }
+ return cards;
+ }
+
+ get canAddNetworkIPUsageInfo() {
+ return (
+ this.isAdminPage || this.currentProjectId === this.detailData.project_id
+ );
+ }
+
+ get networkCard() {
+ const options = [
+ {
+ label: t('Network Name'),
+ dataIndex: 'network.name',
+ },
+ {
+ label: t('Network ID'),
+ dataIndex: 'network.id',
+ render: (value) => {
+ const link = this.getLinkRender('networkDetail', value, {
+ id: value,
+ });
+ return link;
+ },
+ },
+ ];
+ return {
+ title: t('Network Info'),
+ options,
+ };
+ }
+
+ get baseInfoCard() {
+ const options = [
+ {
+ label: t('Gateway IP'),
+ dataIndex: 'gateway_ip',
+ },
+ {
+ label: t('Allocation Pools'),
+ dataIndex: 'allocation_pools',
+ render: (value) => {
+ const items = (value || []).map((it) => {
+ const { start, end } = it;
+ return (
+
+ {start} - {end}
+
+ );
+ });
+ return <>{items}>;
+ },
+ },
+ {
+ label: t('Enable DHCP'),
+ dataIndex: 'enable_dhcp',
+ valueRender: 'yesNo',
+ },
+ {
+ label: t('Host Routes'),
+ dataIndex: 'host_routes',
+ render: (value) => {
+ if (!value.length) {
+ return '-';
+ }
+ const lines = value.map((it) => {
+ const { destination, nexthop } = it;
+ return (
+
+ {destination},{nexthop}
+
+ );
+ });
+ return <>{lines}>;
+ },
+ },
+ {
+ label: t('DNS Nameservers'),
+ dataIndex: 'dns_nameservers',
+ render: (value) => {
+ if (!value.length) {
+ return '-';
+ }
+ const lines = value.map((it) => {it}
);
+ return <>{lines}>;
+ },
+ },
+ ];
+ return {
+ title: t('Base Info'),
+ options,
+ };
+ }
+
+ get ipUsageCard() {
+ if (!this.canAddNetworkIPUsageInfo) {
+ return null;
+ }
+ const options = [
+ {
+ label: t('Total IPs'),
+ dataIndex: 'total_ips',
+ },
+ {
+ label: t('Used IPs'),
+ dataIndex: 'used_ips',
+ },
+ ];
+ return {
+ title: t('IP Usage'),
+ options,
+ };
+ }
+}
+
+export default inject('rootStore')(observer(BaseDetail));
diff --git a/src/pages/network/containers/Subnet/Detail/index.jsx b/src/pages/network/containers/Subnet/Detail/index.jsx
new file mode 100644
index 00000000..0142e6e3
--- /dev/null
+++ b/src/pages/network/containers/Subnet/Detail/index.jsx
@@ -0,0 +1,108 @@
+// Copyright 2022 99cloud
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { inject, observer } from 'mobx-react';
+import Base from 'containers/TabDetail';
+import { SubnetStore } from 'stores/neutron/subnet';
+import Port from 'pages/network/containers/Port';
+import Detail from './Detail';
+import actionConfigs from '../actions';
+
+export class SubnetDetail extends Base {
+ get name() {
+ return t('subnet');
+ }
+
+ get policy() {
+ return 'get_subnet';
+ }
+
+ get listUrl() {
+ const { networkId } = this.params;
+ return this.getRoutePath(
+ 'networkDetail',
+ { id: networkId },
+ { tab: 'subnets' }
+ );
+ }
+
+ get actionConfigs() {
+ return actionConfigs;
+ }
+
+ updateFetchParams = (params) => {
+ return {
+ ...params,
+ inDetail: true,
+ };
+ };
+
+ get detailInfos() {
+ const ret = [
+ {
+ title: t('Name'),
+ dataIndex: 'name',
+ },
+ {
+ title: t('Project ID'),
+ dataIndex: 'project_id',
+ },
+ {
+ title: t('CIDR'),
+ dataIndex: 'cidr',
+ },
+ {
+ title: t('IP Version'),
+ dataIndex: 'ip_version',
+ },
+ {
+ title: t('Created At'),
+ dataIndex: 'created_at',
+ valueRender: 'toLocalTime',
+ },
+ {
+ title: t('Update At'),
+ dataIndex: 'updated_at',
+ valueRender: 'toLocalTime',
+ },
+ {
+ title: t('Description'),
+ dataIndex: 'description',
+ },
+ ];
+ return ret;
+ }
+
+ get tabs() {
+ const tabs = [
+ {
+ title: t('Detail'),
+ key: 'detail',
+ component: Detail,
+ },
+ {
+ title: t('Ports'),
+ key: 'ports',
+ component: Port,
+ },
+ ];
+ return tabs;
+ }
+
+ init() {
+ this.store = new SubnetStore();
+ }
+}
+
+export default inject('rootStore')(observer(SubnetDetail));
diff --git a/src/pages/network/containers/Network/Detail/subnetActions/DeleteSubnet.jsx b/src/pages/network/containers/Subnet/actions/Delete.jsx
similarity index 95%
rename from src/pages/network/containers/Network/Detail/subnetActions/DeleteSubnet.jsx
rename to src/pages/network/containers/Subnet/actions/Delete.jsx
index 6d6e2d3d..8eb8ba33 100644
--- a/src/pages/network/containers/Network/Detail/subnetActions/DeleteSubnet.jsx
+++ b/src/pages/network/containers/Subnet/actions/Delete.jsx
@@ -15,7 +15,7 @@
import { ConfirmAction } from 'containers/Action';
import globalSubnetStore from 'stores/neutron/subnet';
-export default class DeleteAction extends ConfirmAction {
+export default class Delete extends ConfirmAction {
get id() {
return 'delete';
}
diff --git a/src/pages/network/containers/Network/Detail/subnetActions/EditSubnet.jsx b/src/pages/network/containers/Subnet/actions/Edit.jsx
similarity index 98%
rename from src/pages/network/containers/Network/Detail/subnetActions/EditSubnet.jsx
rename to src/pages/network/containers/Subnet/actions/Edit.jsx
index caa0285f..159d1c87 100644
--- a/src/pages/network/containers/Network/Detail/subnetActions/EditSubnet.jsx
+++ b/src/pages/network/containers/Subnet/actions/Edit.jsx
@@ -16,7 +16,7 @@ import { inject, observer } from 'mobx-react';
import { ModalAction } from 'containers/Action';
import { ipValidate } from 'utils/validate';
import globalSubnetStore from 'stores/neutron/subnet';
-import networkUtil from '../../actions/networkUtil';
+import networkUtil from '../../Network/actions/networkUtil';
const {
checkAllocation_pools,
diff --git a/src/pages/network/containers/Network/Detail/subnetActions/index.jsx b/src/pages/network/containers/Subnet/actions/index.jsx
similarity index 72%
rename from src/pages/network/containers/Network/Detail/subnetActions/index.jsx
rename to src/pages/network/containers/Subnet/actions/index.jsx
index e04e041d..3d1d6293 100644
--- a/src/pages/network/containers/Network/Detail/subnetActions/index.jsx
+++ b/src/pages/network/containers/Subnet/actions/index.jsx
@@ -12,21 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import CreateSubnet from '../../actions/CreateSubnet';
-import DeleteAction from './DeleteSubnet';
-import EditSubnet from './EditSubnet';
+import Create from '../../Network/actions/CreateSubnet';
+import Delete from './Delete';
+import Edit from './Edit';
const actionConfigs = {
rowActions: {
- firstAction: EditSubnet,
+ firstAction: Edit,
moreActions: [
{
- action: DeleteAction,
+ action: Delete,
},
],
},
- batchActions: [DeleteAction],
- primaryActions: [CreateSubnet],
+ batchActions: [Delete],
+ primaryActions: [Create],
};
export default actionConfigs;
diff --git a/src/pages/network/containers/Network/Detail/Subnets.jsx b/src/pages/network/containers/Subnet/index.jsx
similarity index 60%
rename from src/pages/network/containers/Network/Detail/Subnets.jsx
rename to src/pages/network/containers/Subnet/index.jsx
index d6cbd275..25f977ce 100644
--- a/src/pages/network/containers/Network/Detail/Subnets.jsx
+++ b/src/pages/network/containers/Subnet/index.jsx
@@ -12,54 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import React from 'react';
import { observer, inject } from 'mobx-react';
import Base from 'containers/List';
import { SubnetStore } from 'stores/neutron/subnet';
-import { toJS } from 'mobx';
-import globalRootStore from 'stores/root';
-import actionConfigs from './subnetActions';
+import actionConfigs from './actions';
// import { networkStatus } from 'resources/network';
export class Subnets extends Base {
init() {
this.store = new SubnetStore();
- const { detail: { subnet_ip_availability = [] } = {} } = this.props;
- this.subnet_ip_availability = subnet_ip_availability;
}
- getDataSource = () => {
- const { data, filters = {}, timeFilter = {} } = this.list;
- const { id, tab, ...rest } = filters;
- const newFilters = rest;
- let items = [];
- if (this.isFilterByBackend) {
- items = toJS(data);
- } else {
- items = (toJS(data) || []).filter((it) =>
- this.filterData(it, toJS(newFilters), toJS(timeFilter))
- );
- this.updateList({ total: items.length });
- }
- const hasTransData = items.some((item) =>
- this.itemInTransitionFunction(item)
- );
- if (hasTransData) {
- this.setRefreshDataTimerTransition();
- } else {
- this.setRefreshDataTimerAuto();
- }
- const ret = items.map((item) => {
- const usageDetail = this.subnet_ip_availability.find(
- (i) => i.subnet_id === item.id
- );
- return {
- ...usageDetail,
- ...item,
- };
- });
- return ret;
- };
-
get policy() {
return 'get_subnet';
}
@@ -77,16 +41,16 @@ export class Subnets extends Base {
}
updateFetchParams = () => {
- const { id } = this.props.match.params;
return {
- network_id: id,
+ network_id: this.id,
+ network: this.props.detail,
+ all_projects: this.isAdminPage,
};
};
get canAddNetworkIPUsageInfo() {
return (
- this.isAdminPage ||
- globalRootStore.user.project.id === this.props.detail.project_id
+ this.isAdminPage || this.currentProjectId === this.props.detail.project_id
);
}
@@ -96,6 +60,11 @@ export class Subnets extends Base {
title: t('Name'),
dataIndex: 'name',
stringify: (name, record) => name || record.id,
+ routeName: this.getRouteName('subnetDetail'),
+ routeParamsFunc: (data) => ({
+ networkId: data.network_id,
+ id: data.id,
+ }),
},
{
title: t('CIDR'),
@@ -113,11 +82,25 @@ export class Subnets extends Base {
isHideable: true,
},
{
- // title: t('Status'),
- // dataIndex: 'status',
- // render: value => networkStatus[value] || value,
- // isHideable: true,
- // }, {
+ title: t('Port Count'),
+ dataIndex: 'subnetPorts',
+ isHideable: true,
+ stringify: (value) => (value || []).length,
+ render: (value, record) => {
+ const count = (value || []).length;
+ if (!count) {
+ return '-';
+ }
+ const link = this.getLinkRender(
+ 'subnetDetail',
+ count,
+ { id: record.id, networkId: record.network_id },
+ { tab: 'ports' }
+ );
+ return <>{link}>;
+ },
+ },
+ {
title: t('Created At'),
dataIndex: 'created_at',
valueRender: 'toLocalTime',
@@ -126,7 +109,7 @@ export class Subnets extends Base {
];
if (this.canAddNetworkIPUsageInfo) {
ret.splice(
- 4,
+ 5,
0,
{
title: t('Total IPs'),
diff --git a/src/pages/network/routes/index.js b/src/pages/network/routes/index.js
index ec3b37d7..bda54af5 100644
--- a/src/pages/network/routes/index.js
+++ b/src/pages/network/routes/index.js
@@ -17,6 +17,7 @@ import E404 from 'pages/base/containers/404';
import Network from '../containers/Network';
import AdminNetwork from '../containers/Network/Network';
import NetworkDetail from '../containers/Network/Detail';
+import SubnetDetail from '../containers/Subnet/Detail';
import Router from '../containers/Router';
import FloatingIp from '../containers/FloatingIp';
import FloatingIpDetail from '../containers/FloatingIp/Detail';
@@ -58,6 +59,46 @@ export default [
component: NetworkDetail,
exact: true,
},
+ {
+ path: `${PATH}/networks/detail/:networkId/subnet/:id`,
+ component: SubnetDetail,
+ exact: true,
+ },
+ {
+ path: `${PATH}/networks-admin/detail/:networkId/subnet/:id`,
+ component: SubnetDetail,
+ exact: true,
+ },
+ {
+ path: `${PATH}/networks/detail/:networkId/port/:id`,
+ component: PortDetail,
+ exact: true,
+ },
+ {
+ path: `${PATH}/networks-admin/detail/:networkId/port/:id`,
+ component: PortDetail,
+ exact: true,
+ },
+ {
+ path: `${PATH}/networks/detail/:networkId/subnet/:subnetId/port/:id`,
+ component: PortDetail,
+ exact: true,
+ },
+ {
+ path: `${PATH}/networks-admin/detail/:networkId/subnet/:subnetId/port/:id`,
+ component: PortDetail,
+ exact: true,
+ },
+ {
+ path: `${PATH}/instance/detail/:instanceId/port/:id`,
+ component: PortDetail,
+ exact: true,
+ },
+ {
+ path: `${PATH}/instance-admin/detail/:instanceId/port/:id`,
+ component: PortDetail,
+ exact: true,
+ },
{ path: `${PATH}/router`, component: Router, exact: true },
{ path: `${PATH}/router-admin`, component: Router, exact: true },
{
diff --git a/src/stores/neutron/port-extension.js b/src/stores/neutron/port-extension.js
index d14ae12e..f92c211f 100644
--- a/src/stores/neutron/port-extension.js
+++ b/src/stores/neutron/port-extension.js
@@ -59,6 +59,24 @@ export class PortStore extends Base {
};
}
+ get paramsFunc() {
+ return (params, all_projects) => {
+ const { current, device_owner, subnetId, networkId, ...rest } = params;
+ const newParams = { ...rest };
+ if (device_owner && isString(device_owner)) {
+ if (device_owner === 'none') {
+ newParams.device_owner = [''];
+ } else {
+ newParams.device_owner = device_owner.split(',');
+ }
+ }
+ if (!all_projects) {
+ newParams.tenant_id = this.currentProjectId;
+ }
+ return newParams;
+ };
+ }
+
@observable
fixed_ips = new List();
@@ -150,6 +168,37 @@ export class PortStore extends Base {
item.itemInList = itemContrib;
return item;
}
+
+ async listDidFetch(items, allProjects, filters) {
+ if (!items.length) {
+ return items;
+ }
+ const { subnetId } = filters;
+ if (!subnetId) {
+ return items;
+ }
+ const newItems = [];
+ items.forEach((it) => {
+ const { fixed_ips = [] } = it;
+ const newFixedIps = fixed_ips.filter((ip) => ip.subnet_id === subnetId);
+ if (newFixedIps.length) {
+ const ipv4 = it.ipv4.filter((ip) =>
+ newFixedIps.some((newIp) => newIp.ip_address === ip)
+ );
+ const ipv6 = it.ipv6.filter((ip) =>
+ newFixedIps.some((newIp) => newIp.ip_address === ip)
+ );
+ newItems.push({
+ ...it,
+ fixed_ips: newFixedIps,
+ ipv4,
+ ipv6,
+ subnet_id: subnetId,
+ });
+ }
+ });
+ return newItems;
+ }
}
const globalPortStore = new PortStore();
diff --git a/src/stores/neutron/subnet.js b/src/stores/neutron/subnet.js
index cf340880..36935ac6 100644
--- a/src/stores/neutron/subnet.js
+++ b/src/stores/neutron/subnet.js
@@ -25,6 +25,17 @@ export class SubnetStore extends Base {
return false;
}
+ get portClient() {
+ return client.neutron.ports;
+ }
+
+ get paramsFunc() {
+ return (params) => {
+ const { network, ...rest } = params;
+ return rest;
+ };
+ }
+
@action
async update({ id }, values) {
const {
@@ -47,6 +58,62 @@ export class SubnetStore extends Base {
};
return this.submitting(this.client.update(id, { subnet: data }));
}
+
+ async listDidFetch(items, allProjects, filters) {
+ if (!items.length) {
+ return items;
+ }
+ const {
+ network: { id: networkId, subnet_ip_availability: ipUsage = [] } = {},
+ } = filters;
+ const portParams = {
+ network_id: networkId,
+ };
+ if (!allProjects) {
+ portParams.tenant_id = this.currentProjectId;
+ }
+ const { ports = [] } = await this.portClient.list(portParams);
+ return items.map((it) => {
+ const ipInfo = ipUsage.find((u) => u.subnet_id === it.id);
+ const subnetPorts = ports.filter((port) => {
+ return port.fixed_ips.find((ip) => ip.subnet_id === it.id);
+ });
+ const { total_ips, used_ips } = ipInfo || {};
+ return {
+ ...it,
+ total_ips,
+ used_ips,
+ subnetPorts,
+ };
+ });
+ }
+
+ async detailDidFetch(item, allProjects, filters) {
+ const { inDetail = false } = filters;
+ if (!inDetail) {
+ return item;
+ }
+ const { network_id, id } = item;
+ const networkParams = {
+ id: network_id,
+ isAdminPage: allProjects,
+ currentProjectId: this.currentProjectId,
+ };
+ const { NetworkStore } = require('stores/neutron/network');
+ const network =
+ await new NetworkStore().fetchDetailWithAvailabilityAndUsage(
+ networkParams
+ );
+ const { subnet_ip_availability = [] } = network;
+ const ipAvailability = subnet_ip_availability.find(
+ (it) => it.subnet_id === id
+ );
+ return {
+ ...item,
+ network,
+ ...ipAvailability,
+ };
+ }
}
const globalSubnetStore = new SubnetStore();