From 2b53286eb0bff0e5a47f7fba386c2fa05170edfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Numan=20MENEK=C5=9EE?= Date: Thu, 24 Mar 2022 11:52:30 +0000 Subject: [PATCH] Add Magnum UI to Skyline-Console Change-Id: I6ed9def8d7a9fa25b9c43d26cf6d3a1f573f5d19 --- src/client/client/constants.js | 2 + src/client/index.js | 2 + src/client/magnum/index.js | 45 ++++ src/layouts/menu.jsx | 58 +++++ src/pages/basic/routes/index.js | 7 + src/pages/container-infra/App.jsx | 21 ++ .../ClusterTemplates/Detail/BaseDetail.jsx | 183 ++++++++++++++++ .../ClusterTemplates/Detail/index.jsx | 65 ++++++ .../ClusterTemplates/actions/Delete.jsx | 42 ++++ .../ClusterTemplates/actions/EditStep.jsx | 45 ++++ .../actions/StepCreate/StepInfo/index.jsx | 118 ++++++++++ .../actions/StepCreate/StepLabel/index.jsx | 39 ++++ .../actions/StepCreate/StepNetwork/index.jsx | 207 ++++++++++++++++++ .../actions/StepCreate/StepNodeSpec/index.jsx | 198 +++++++++++++++++ .../actions/StepCreate/index.jsx | 147 +++++++++++++ .../ClusterTemplates/actions/index.jsx | 27 +++ .../containers/ClusterTemplates/index.jsx | 65 ++++++ .../containers/Clusters/Detail/BaseDetail.jsx | 181 +++++++++++++++ .../containers/Clusters/Detail/index.jsx | 84 +++++++ .../containers/Clusters/actions/Delete.jsx | 44 ++++ .../containers/Clusters/actions/Resize.jsx | 60 +++++ .../actions/StepCreate/StepAdvanced/index.jsx | 42 ++++ .../actions/StepCreate/StepDetails/index.jsx | 112 ++++++++++ .../StepCreate/StepManagement/index.jsx | 50 +++++ .../actions/StepCreate/StepNetworks/index.jsx | 124 +++++++++++ .../actions/StepCreate/StepSize/index.jsx | 78 +++++++ .../Clusters/actions/StepCreate/index.jsx | 116 ++++++++++ .../containers/Clusters/actions/index.jsx | 25 +++ .../containers/Clusters/index.jsx | 66 ++++++ src/pages/container-infra/routes/index.js | 49 +++++ src/stores/magnum/clusterTemplates.js | 51 +++++ src/stores/magnum/clusters.js | 62 ++++++ 32 files changed, 2415 insertions(+) create mode 100644 src/client/magnum/index.js create mode 100644 src/pages/container-infra/App.jsx create mode 100644 src/pages/container-infra/containers/ClusterTemplates/Detail/BaseDetail.jsx create mode 100644 src/pages/container-infra/containers/ClusterTemplates/Detail/index.jsx create mode 100644 src/pages/container-infra/containers/ClusterTemplates/actions/Delete.jsx create mode 100644 src/pages/container-infra/containers/ClusterTemplates/actions/EditStep.jsx create mode 100644 src/pages/container-infra/containers/ClusterTemplates/actions/StepCreate/StepInfo/index.jsx create mode 100644 src/pages/container-infra/containers/ClusterTemplates/actions/StepCreate/StepLabel/index.jsx create mode 100644 src/pages/container-infra/containers/ClusterTemplates/actions/StepCreate/StepNetwork/index.jsx create mode 100644 src/pages/container-infra/containers/ClusterTemplates/actions/StepCreate/StepNodeSpec/index.jsx create mode 100644 src/pages/container-infra/containers/ClusterTemplates/actions/StepCreate/index.jsx create mode 100644 src/pages/container-infra/containers/ClusterTemplates/actions/index.jsx create mode 100644 src/pages/container-infra/containers/ClusterTemplates/index.jsx create mode 100644 src/pages/container-infra/containers/Clusters/Detail/BaseDetail.jsx create mode 100644 src/pages/container-infra/containers/Clusters/Detail/index.jsx create mode 100644 src/pages/container-infra/containers/Clusters/actions/Delete.jsx create mode 100644 src/pages/container-infra/containers/Clusters/actions/Resize.jsx create mode 100644 src/pages/container-infra/containers/Clusters/actions/StepCreate/StepAdvanced/index.jsx create mode 100644 src/pages/container-infra/containers/Clusters/actions/StepCreate/StepDetails/index.jsx create mode 100644 src/pages/container-infra/containers/Clusters/actions/StepCreate/StepManagement/index.jsx create mode 100644 src/pages/container-infra/containers/Clusters/actions/StepCreate/StepNetworks/index.jsx create mode 100644 src/pages/container-infra/containers/Clusters/actions/StepCreate/StepSize/index.jsx create mode 100644 src/pages/container-infra/containers/Clusters/actions/StepCreate/index.jsx create mode 100644 src/pages/container-infra/containers/Clusters/actions/index.jsx create mode 100644 src/pages/container-infra/containers/Clusters/index.jsx create mode 100644 src/pages/container-infra/routes/index.js create mode 100644 src/stores/magnum/clusterTemplates.js create mode 100644 src/stores/magnum/clusters.js diff --git a/src/client/client/constants.js b/src/client/client/constants.js index 9a951527..de36d96b 100644 --- a/src/client/client/constants.js +++ b/src/client/client/constants.js @@ -33,6 +33,7 @@ export const endpointVersionMap = { trove: 'v1.0', manilav2: 'v2', barbican: 'v1', + magnum : 'v1', }; export const endpointsDefault = { @@ -71,6 +72,7 @@ export const swiftBase = () => getOpenstackEndpoint('swift'); export const troveBase = () => getOpenstackEndpoint('trove'); export const manilaBase = () => getOpenstackEndpoint('manilav2'); export const barbicanBase = () => getOpenstackEndpoint('barbican'); +export const magnumBase = () => getOpenstackEndpoint('magnum'); export const ironicOriginEndpoint = () => getOriginEndpoint('ironic'); export const vpnEndpoint = () => getOriginEndpoint('neutron_vpn'); diff --git a/src/client/index.js b/src/client/index.js index 05540312..a161cf61 100644 --- a/src/client/index.js +++ b/src/client/index.js @@ -26,6 +26,7 @@ import swift from './swift'; import trove from './trove'; import manila from './manila'; import barbican from './barbican'; +import magnum from './magnum'; const client = { skyline, @@ -42,6 +43,7 @@ const client = { trove, manila, barbican, + magnum }; window.client = client; diff --git a/src/client/magnum/index.js b/src/client/magnum/index.js new file mode 100644 index 00000000..73334e63 --- /dev/null +++ b/src/client/magnum/index.js @@ -0,0 +1,45 @@ +// 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 Base from '../client/base'; +import { magnumBase } from '../client/constants'; + +class MagnumClient extends Base { + get baseUrl() { + return magnumBase(); + } + + get resources() { + return [ + { + name: 'clusters', + key: 'clusters', + responseKey: 'cluster', + extendOperations: [ + { + name: 'resize', + key: 'actions/resize', + method: 'post', + }, + ] + }, + { + name: 'clusterTemplates', + key: 'clustertemplates', + responseKey: 'clustertemplate', + }, + ]; + } +} + +const magnumClient = new MagnumClient(); +export default magnumClient; \ No newline at end of file diff --git a/src/layouts/menu.jsx b/src/layouts/menu.jsx index 371eff65..d4779b5c 100644 --- a/src/layouts/menu.jsx +++ b/src/layouts/menu.jsx @@ -22,6 +22,7 @@ import { DatabaseFilled, AppstoreOutlined, SwitcherOutlined, + ContainerOutlined, } from '@ant-design/icons'; const renderMenu = (t) => { @@ -580,6 +581,63 @@ const renderMenu = (t) => { }, ], }, + { + path: '/container-infra', + name: t('Container Infra'), + key: 'containerInfra', + icon: , + children: [ + { + path: '/container-infra/clusters', + name: t('Clusters'), + key: 'containerInfraClusters', + level: 1, + children: [ + { + path: /^\/container-infra\/clusters\/detail\/.[^/]+$/, + name: t('Cluster Detail'), + key: 'containerInfraClusterDetail', + level: 2, + routePath: '/container-infra/clusters/detail/:id', + }, + { + path: '/container-infra/clusters/create', + name: t('Create Cluster'), + key: 'containerInfraCreateCluster', + level: 2, + }, + ], + }, + { + path: '/container-infra/cluster-template', + name: t('Cluster Template'), + key: 'clusterTemplate', + level: 1, + children: [ + { + path: /^\/container-infra\/cluster-template\/detail\/.[^/]+$/, + name: t('Cluster Template Detail'), + key: 'containerInfraClusterTemplateDetail', + level: 2, + routePath: '/container-infra/cluster-template/detail/:id', + }, + { + path: '/container-infra/cluster-template/create', + name: t('Create Cluster Template'), + key: 'containerInfraCreateClusterTemplate', + level: 2, + }, + { + path: /^\/container-infra\/cluster-template\/update\/.[^/]+\/.[^/]+$/, + name: t('Update Cluster Template'), + key: 'containerInfraUpdateClusterTemplate', + level: 2, + routePath: '/container-infra/cluster-template/update/:id', + }, + ], + }, + ], + }, ]; return menu; }; diff --git a/src/pages/basic/routes/index.js b/src/pages/basic/routes/index.js index c01cddf7..32bc811d 100644 --- a/src/pages/basic/routes/index.js +++ b/src/pages/basic/routes/index.js @@ -50,6 +50,9 @@ const Database = lazy(() => const Share = lazy(() => import(/* webpackChunkName: "share" */ 'pages/share/App') ); +const ContainerInfra = lazy(() => + import(/* webpackChunkName: "container-infra" */ 'pages/container-infra/App') +); const E404 = lazy(() => import(/* webpackChunkName: "E404" */ 'pages/base/containers/404') ); @@ -102,6 +105,10 @@ export default [ path: `/share`, component: Share, }, + { + path: `/container-infra`, + component: ContainerInfra, + }, { path: '*', component: E404 }, ], }, diff --git a/src/pages/container-infra/App.jsx b/src/pages/container-infra/App.jsx new file mode 100644 index 00000000..014bcad1 --- /dev/null +++ b/src/pages/container-infra/App.jsx @@ -0,0 +1,21 @@ +// Copyright 2021 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 renderRoutes from 'utils/RouterConfig'; + +import routes from './routes'; + +const App = (props) => renderRoutes(routes, props); + +export default App; diff --git a/src/pages/container-infra/containers/ClusterTemplates/Detail/BaseDetail.jsx b/src/pages/container-infra/containers/ClusterTemplates/Detail/BaseDetail.jsx new file mode 100644 index 00000000..ce9dc6bb --- /dev/null +++ b/src/pages/container-infra/containers/ClusterTemplates/Detail/BaseDetail.jsx @@ -0,0 +1,183 @@ +// 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 Base from 'containers/BaseDetail'; +import React from 'react'; +import { Link } from 'react-router-dom'; +import { inject, observer } from 'mobx-react'; + +export class BaseDetail extends Base { + get leftCards() { + return [this.baseInfoCard, this.networkCard]; + } + + get rightCards() { + return [this.specCard, this.labelCard]; + } + + get baseInfoCard() { + const options = [ + { + label: t('COE'), + dataIndex: 'coe' + }, + { + label: t('Cluster Distro'), + dataIndex: 'cluster_distro' + }, + { + label: t('Server Type'), + dataIndex: 'server_type' + }, + { + label: t('Public'), + dataIndex: 'public', + valueRender: 'yesNo' + }, + { + label: t('Registry Enabled'), + dataIndex: 'registry_enabled', + valueRender: 'yesNo' + }, + { + label: t('TLS Disabled'), + dataIndex: 'tls_disabled', + valueRender: 'yesNo' + }, + ]; + + return { + title: t('Cluster Type'), + options + }; + } + + get networkCard() { + const options = [ + { + label: t('Network Driver'), + dataIndex: 'network_driver' + }, + { + label: t('HTTP Proxy'), + dataIndex: 'http_proxy' + }, + { + label: t('HTTPS Proxy'), + dataIndex: 'https_proxy' + }, + { + label: t('No Proxy'), + dataIndex: 'no_proxy' + }, + { + label: t('External Network ID'), + dataIndex: 'external_network_id' + }, + { + label: t('Fixed Network'), + dataIndex: 'fixed_network' + }, + { + label: t('Fixed Subnet'), + dataIndex: 'fixed_subnet' + }, + { + label: t('DNS'), + dataIndex: 'dns_nameserver' + }, + { + label: t('Master LB Enabled'), + dataIndex: 'master_lb_enabled', + valueRender: 'yesNo' + }, + { + label: t('Floating IP Enabled'), + dataIndex: 'floating_ip_enabled', + valueRender: 'yesNo' + }, + ]; + + return { + title: t('Network'), + options, + }; + } + + get specCard() { + const image = this.detailData.image_id; + const imageUrl = this.getRoutePath('imageDetail', { id: image }); + + const keypair = this.detailData.keypair_id; + const keypairUrl = this.getRoutePath('keypairDetail', { id: keypair }); + + const options = [ + { + label: t('Image ID'), + content: {image} + }, + { + label: t('Keypair'), + content: {keypair} + }, + { + label: t('Flavor ID'), + dataIndex: 'flavor_id' + }, + { + label: t('Master Flavor ID'), + dataIndex: 'master_flavor_id' + }, + { + label: t('Volume Driver'), + dataIndex: 'volume_driver' + }, + { + label: t('Docker Storage Driver'), + dataIndex: 'docker_storage_driver' + }, + { + label: t('Docker Volume Size'), + dataIndex: 'docker_volume_size' + }, + { + label: t('Insecure Registry'), + dataIndex: 'insecure_registry' + }, + ]; + + return { + title: t('Node Spec'), + options, + }; + } + + get labelCard() { + const options = [ + { + label: t('labels'), + dataIndex: 'labels', + render: (value) => value ? Object.entries(value).map(([key, val]) => { + return
  • {key} : {val}
+ }) : '-' + }, + ]; + + return { + title: t('Labels'), + labelCol: 2, + options, + }; + } +} + +export default inject("rootStore")(observer(BaseDetail)) \ No newline at end of file diff --git a/src/pages/container-infra/containers/ClusterTemplates/Detail/index.jsx b/src/pages/container-infra/containers/ClusterTemplates/Detail/index.jsx new file mode 100644 index 00000000..8c415a44 --- /dev/null +++ b/src/pages/container-infra/containers/ClusterTemplates/Detail/index.jsx @@ -0,0 +1,65 @@ +// 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 BaseDetail from './BaseDetail'; +import globalClusterTemplateStore from 'src/stores/magnum/clusterTemplates'; + +export class ClusterTemplateDetail extends Base { + init() { + this.store = globalClusterTemplateStore; + } + + get name() { + return t('Cluster Template Detail'); + } + + get listUrl() { + return this.getRoutePath('clusterTemplate'); + } + + get policy() { + return 'container-infra:clustertemplate:detail'; + } + + get detailInfos() { + return [ + { + title: t('Name'), + dataIndex: 'name', + }, + { + title: t('Created'), + dataIndex: 'created_at', + valueRender: 'toLocalTime' + }, + { + title: t('Updated'), + dataIndex: 'updated_at', + valueRender: 'toLocalTime' + }, + ]; + } + + get tabs() { + return [ + { + title: t('General Info'), + key: 'general_info', + component: BaseDetail, + }, + ]; + } +} + +export default inject("rootStore")(observer(ClusterTemplateDetail)) \ No newline at end of file diff --git a/src/pages/container-infra/containers/ClusterTemplates/actions/Delete.jsx b/src/pages/container-infra/containers/ClusterTemplates/actions/Delete.jsx new file mode 100644 index 00000000..7527fd96 --- /dev/null +++ b/src/pages/container-infra/containers/ClusterTemplates/actions/Delete.jsx @@ -0,0 +1,42 @@ +// 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 { ConfirmAction } from 'containers/Action'; +import globalClusterTemplateStore from 'stores/magnum/clusterTemplates'; + +export default class DeleteClusterTemplates extends ConfirmAction { + get id() { + return 'delete'; + } + + get title() { + return t('Delete Template') + } + + get actionName() { + return t('Delete Clusters Templates'); + } + + get buttonType() { + return 'danger'; + } + + get buttonText() { + return t('Delete'); + } + + policy = 'container-infra:clustertemplate:delete'; + + allowedCheckFunc = () => true; + + onSubmit = (data) => globalClusterTemplateStore.delete({ id: data.uuid }); +} diff --git a/src/pages/container-infra/containers/ClusterTemplates/actions/EditStep.jsx b/src/pages/container-infra/containers/ClusterTemplates/actions/EditStep.jsx new file mode 100644 index 00000000..46af33fa --- /dev/null +++ b/src/pages/container-infra/containers/ClusterTemplates/actions/EditStep.jsx @@ -0,0 +1,45 @@ +// Copyright 2021 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 { getPath } from 'src/utils/route-map'; +import StepCreate from './StepCreate'; + +@inject('rootStore') +@observer +export default class Edit extends StepCreate { + static id = 'update-cluster-template'; + + static title = t('Update Cluster Template'); + + get name() { + return t('Update Cluster Template'); + } + + get listUrl() { + return this.getRoutePath('clusterTemplate'); + } + + static policy = 'container-infra:clustertemplate:update'; + + static path = (item) => { + const key = 'containerInfraUpdateClusterTemplate'; + const { id } = item; + return getPath({ key, params: { id } }); + }; + + static allowed() { + return Promise.resolve(true); + } +} \ No newline at end of file diff --git a/src/pages/container-infra/containers/ClusterTemplates/actions/StepCreate/StepInfo/index.jsx b/src/pages/container-infra/containers/ClusterTemplates/actions/StepCreate/StepInfo/index.jsx new file mode 100644 index 00000000..b45565e4 --- /dev/null +++ b/src/pages/container-infra/containers/ClusterTemplates/actions/StepCreate/StepInfo/index.jsx @@ -0,0 +1,118 @@ +// Copyright 2021 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 Base from "components/Form"; +import { inject, observer } from "mobx-react"; + +export class StepInfo extends Base { + get title() { + return t("Info") + } + + get name() { + return t("Info") + } + + get isEdit() { + return !!this.props.extra; + } + + get isStep() { + return true; + } + + onCOEChange = (value) => { + this.updateContext({ + coeSelectRows: value, + }); + } + + get defaultValue() { + const values = {}; + + if (this.isEdit) { + values.clusterTemplateName = this.props.extra.name; + values.coe = this.props.extra.coe; + values.cluster_template_public = this.props.extra.public; + values.cluster_template_hidden = this.props.extra.hidden; + values.docker_registry_enabled = this.props.extra.registry_enabled; + values.tls_disabled = this.props.extra.tls_disabled; + } + return values; + } + + get formItems() { + return [ + { + name: "clusterTemplateName", + label: t("Cluster Template Name"), + type: "input", + placeholder: t("Cluster Template Name"), + required: true + }, + { + name: "coe", + label: t("Container Orchestration Engine"), + type: "select", + options: [ + { + label: t("Kubernetes"), + value: "kubernetes" + }, + { + label: t("Docker Swarm"), + value: "swarm" + }, + { + label: t("Docker Swarm Mode"), + value: "swarm-mode" + }, + { + label: t("Mesos"), + value: "mesos" + }, + { + label: t("DC/OS"), + value: "dcos" + }, + ], + onChange: this.onCOEChange, + allowClear: true, + showSearch: true + }, + { + name: "cluster_template_public", + label: t("Public"), + type: "check" + }, + { + name: "cluster_template_hidden", + label: t("Hidden"), + type: "check" + }, + { + name: "docker_registry_enabled", + label: t("Enable Registry"), + type: "check" + }, + { + name: "tls_disabled", + label: t("Disable TLS"), + type: "check" + } + ] + } +} + +export default inject("rootStore")(observer(StepInfo)) \ No newline at end of file diff --git a/src/pages/container-infra/containers/ClusterTemplates/actions/StepCreate/StepLabel/index.jsx b/src/pages/container-infra/containers/ClusterTemplates/actions/StepCreate/StepLabel/index.jsx new file mode 100644 index 00000000..e0e12edf --- /dev/null +++ b/src/pages/container-infra/containers/ClusterTemplates/actions/StepCreate/StepLabel/index.jsx @@ -0,0 +1,39 @@ +// 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 +// +// Unles //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 Base from "components/Form"; +import { inject, observer } from "mobx-react"; +import KeyValueInput from 'components/FormItem/KeyValueInput'; + +export class StepLabel extends Base { + get title() { + return t('Labels'); + } + + get name() { + return t('Labels'); + } + + get formItems() { + return [ + { + name: 'additionalLabels', + label: t('Additional Labels'), + type: 'add-select', + itemComponent: KeyValueInput, + addText: t('Add Label'), + }, + ] + } +} + +export default inject("rootStore")(observer(StepLabel)) diff --git a/src/pages/container-infra/containers/ClusterTemplates/actions/StepCreate/StepNetwork/index.jsx b/src/pages/container-infra/containers/ClusterTemplates/actions/StepCreate/StepNetwork/index.jsx new file mode 100644 index 00000000..303cc4fb --- /dev/null +++ b/src/pages/container-infra/containers/ClusterTemplates/actions/StepCreate/StepNetwork/index.jsx @@ -0,0 +1,207 @@ +// Copyright 2021 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 Base from 'components/Form'; +import { inject, observer } from 'mobx-react'; +import globalNetworkStore from 'src/stores/neutron/network'; +import globalSubnetStore from 'src/stores/neutron/subnet'; + +export class StepNetwork extends Base { + init() { + this.getFloatingIps(); + this.getSubnets(); + this.state = { selectedSubnetId: '' }; + } + + get title() { + return t('Network'); + } + + get name() { + return t('Network'); + } + + get isStep() { + return true; + } + + get isEdit() { + return !!this.props.extra; + } + + async getFloatingIps() { + globalNetworkStore.fetchList(); + } + + get getFloatingIpList() { + return (globalNetworkStore.list.data || []) + .filter((it) => it['router:external'] === true && it.project_id === this.currentProjectId) + .map((it) => ({ + value: it.id, + label: it.name, + })); + } + + get getPrivateFloatingIpList() { + return (globalNetworkStore.list.data || []) + .filter((it) => it['router:external'] === false && it.project_id === this.currentProjectId) + .map((it) => ({ + value: it.id, + label: it.name, + subnetId: it.subnets, + })); + } + + async getSubnets() { + globalSubnetStore.fetchList(); + } + + get getSubnetList() { + return (globalSubnetStore.list.data || []) + .filter((it) => this.state.selectedSubnetId === it.network_id) + .map((it) => ({ + value: it.id, + label: it.name, + })); + } + + onSelectChangeFixedNetwork(value) { + this.setState({ + selectedSubnetId: value, + }); + this.resetFormValue(['fixedSubnet']); + } + + get getNetworkDriver() { + const { context = {} } = this.props; + const { + coeSelectRows = "", + coe = "" + } = context; + let networkDriver = []; + if (!coeSelectRows || !coe) { + networkDriver.push({ val: "docker", name: "Docker" }, { val: "flannel", name: "Flannel" }, { val: "calico", name: "Calico" }) + } + if (coeSelectRows === "swarm" || coeSelectRows === "swarm-mode") { + networkDriver.push({ val: "docker", name: "Docker" }, { val: "flannel", name: "Flannel" }) + } + if (coeSelectRows === "kubernetes") { + networkDriver.push({ val: "calico", name: "Calico" }, { val: "flannel", name: "Flannel" }) + } + if (coeSelectRows === "mesos" || coeSelectRows === "dcos") { + networkDriver.push({ val: "docker", name: "Docker" }) + } + return (networkDriver || []) + .map((it) => ({ + value: it.val, + label: it.name, + })); + } + + get defaultValue() { + const values = {}; + + if (this.isEdit) { + values.networkDriver = this.props.extra.network_driver; + values.HTTPProxy = this.props.extra.http_proxy; + values.HTTPSProxy = this.props.extra.https_proxy; + values.noProxy = this.props.extra.no_proxy; + values.externalNetworkID = this.props.extra.external_network_id; + values.fixedNetwork = this.props.extra.fixed_network; + values.fixedSubnet = this.props.extra.fixed_subnet; + values.DNS = this.props.extra.dns_nameserver; + values.masterLB = this.props.extra.master_lb_enabled; + values.floatingIP = this.props.extra.floating_ip_enabled; + } + return values; + } + + get formItems() { + return [ + { + name: "networkDriver", + label: t("Network Driver"), + placeholder: t("Choose a Network Driver"), + type: "select", + options: this.getNetworkDriver, + allowClear: true, + showSearch: true + }, + { + name: "HTTPProxy", + label: t("HTTP Proxy"), + placeholder: t("The http_proxy address to use for nodes in cluster"), + type: "input" + }, + { + name: "HTTPSProxy", + label: t("HTTPS Proxy"), + placeholder: t("The https_proxy address to use for nodes in cluster"), + type: "input" + }, + { + name: "noProxy", + label: t("No Proxy"), + placeholder: t("The no_proxy address to use for nodes in cluster"), + type: "input" + }, + { + name: "externalNetworkID", + label: t("External Network ID"), + placeholder: t("Choose a External Network ID"), + type: "select", + options: this.getFloatingIpList, + allowClear: true, + showSearch: true + }, + { + name: "fixedNetwork", + label: t("Fixed Network"), + placeholder: t("Choose a Private Network ID"), + type: "select", + options: this.getPrivateFloatingIpList, + onChange: (val) => this.onSelectChangeFixedNetwork(val), + allowClear: true, + showSearch: true + }, + { + name: "fixedSubnet", + label: t("Fixed Subnet"), + placeholder: t("Choose a Private Network at first"), + type: "select", + options: this.getSubnetList, + allowClear: true, + showSearch: true + }, + { + name: "DNS", + label: t("DNS"), + placeholder: t("The DNS nameserver to use for this cluster template"), + type: "input" + }, + { + name: "masterLB", + label: t("Master LB"), + type: "check" + }, + { + name: "floatingIP", + label: t("Floating IP"), + type: "check" + } + ] + } +} + +export default inject("rootStore")(observer(StepNetwork)) \ No newline at end of file diff --git a/src/pages/container-infra/containers/ClusterTemplates/actions/StepCreate/StepNodeSpec/index.jsx b/src/pages/container-infra/containers/ClusterTemplates/actions/StepCreate/StepNodeSpec/index.jsx new file mode 100644 index 00000000..e525aa95 --- /dev/null +++ b/src/pages/container-infra/containers/ClusterTemplates/actions/StepCreate/StepNodeSpec/index.jsx @@ -0,0 +1,198 @@ +// Copyright 2021 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 globalImageStore from "src/stores/glance/image"; +import globalKeypairStore from "src/stores/nova/keypair"; +import Base from "components/Form"; +import { inject, observer } from "mobx-react"; +import React from 'react'; +import FlavorSelectTable from "src/pages/compute/containers/Instance/components/FlavorSelectTable"; + +export class StepNodeSpec extends Base { + init() { + this.getImageOsDistro(); + this.getKeypairs(); + } + + get title() { + return t("Node Spec"); + } + + get name() { + return t("Node Spec"); + } + + async getImageOsDistro() { + globalImageStore.fetchList(); + } + + get isStep() { + return true; + } + + get isEdit() { + return !!this.props.extra; + } + + get getImageOsDistroList() { + return (globalImageStore.list.data || []) + .filter((it) => it.name.indexOf('coreos') >= 0) + .filter((it) => it.owner === this.currentProjectId) + .map((it) => ({ + value: it.id, + label: it.name, + })); + } + + async getKeypairs() { + globalKeypairStore.fetchList(); + } + + get getKeypairList() { + return (globalKeypairStore.list.data || []).map((it) => ({ + value: it.name, + label: it.name, + })); + } + + get getVolumeDriver() { + const { context = {} } = this.props; + const { + coeSelectRows = "", + coe = "" + } = context; + let volumeDriver = []; + if (!coeSelectRows || !coe) { + volumeDriver.push({ val: "cinder", name: "Cinder" }, { val: "rexray", name: "Rexray" }) + } + if (coeSelectRows === "kubernetes") { + volumeDriver.push({ val: "cinder", name: "Cinder" }) + } + else if (coeSelectRows) { + volumeDriver.push({ val: "rexray", name: "Rexray" }) + } + return (volumeDriver || []) + .map((it) => ({ + value: it.val, + label: it.name, + })); + } + + onFlavorChange = (value) => { + this.updateContext({ + flavor: value, + }); + }; + + get defaultValue() { + const values = {}; + + if (this.isEdit) { + values.image = this.props.extra.image_id; + values.keypair = this.props.extra.keypair_id; + values.flavorCurrent = this.props.extra.flavor_id; + values.flavor = this.props.extra.flavor_id; + values.masterFlavor = this.props.extra.master_flavor_id; + values.masterFlavorCurrent = this.props.extra.master_flavor_id; + values.volumeDriver = this.props.extra.volume_driver; + values.dockerStorageDriver = this.props.extra.docker_storage_driver; + values.dockerVolumeSize = this.props.extra.docker_volume_size; + } + return values; + } + + get formItems() { + return [ + { + name: "image", + label: t("Image"), + type: "select", + options: this.getImageOsDistroList, + allowClear: true, + showSearch: true + }, + { + name: "keypair", + label: t("Keypair"), + type: "select", + options: this.getKeypairList, + allowClear: true, + showSearch: true + }, + { + name: 'flavorCurrent', + label: t('Current Flavor'), + type: 'label', + iconType: 'flavor', + }, + { + name: 'flavor', + label: t('Flavor'), + type: 'select-table', + component: ( + + ), + }, + { + name: 'masterFlavorCurrent', + label: t('Current Master Flavor'), + type: 'label', + iconType: 'flavor', + }, + { + name: "masterFlavor", + label: t("Master Flavor"), + type: "select-table", + component: ( + + ), + }, + { + name: "volumeDriver", + label: t("Volume Driver"), + type: "select", + options: this.getVolumeDriver, + allowClear: true, + showSearch: true + }, + { + name: "dockerStorageDriver", + label: t("Docker Storage Driver"), + type: "select", + options: [ + { + label: t("Overlay"), + value: "overlay" + }, + { + label: t("Overlay2"), + value: "overlay2" + } + ], + allowClear: true, + showSearch: true + }, + { + name: "dockerVolumeSize", + label: t("Docker Volume Size (GB)"), + type: "input-number", + min: "1", + placeholder: "Spec" + } + ] + } +} + +export default inject("rootStore")(observer(StepNodeSpec)) \ No newline at end of file diff --git a/src/pages/container-infra/containers/ClusterTemplates/actions/StepCreate/index.jsx b/src/pages/container-infra/containers/ClusterTemplates/actions/StepCreate/index.jsx new file mode 100644 index 00000000..fcd29bc4 --- /dev/null +++ b/src/pages/container-infra/containers/ClusterTemplates/actions/StepCreate/index.jsx @@ -0,0 +1,147 @@ +// 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 StepInfo from "./StepInfo"; +import StepNodeSpec from "./StepNodeSpec"; +import StepNetwork from "./StepNetwork"; +import StepLabel from "./StepLabel"; +import { inject, observer } from "mobx-react"; +import { StepAction } from "src/containers/Action"; +import globalClusterTemplateStore from "src/stores/magnum/clusterTemplates"; +import { toJS } from "mobx"; + +export class StepCreate extends StepAction { + init() { + this.store = globalClusterTemplateStore; + this.getDetail(); + } + + static id = "create-cluster-template"; + + static title = t("Create Cluster Template"); + + static path = "/container-infra/cluster-template/create"; + + static policy = "container-infra:clustertemplate:create"; + + static allowed() { + return Promise.resolve(true); + } + + get name() { + return t("Create Cluster Template"); + } + + get listUrl() { + return this.getRoutePath("clusterTemplate"); + } + + get isEdit() { + const { pathname } = this.props.location; + return pathname.indexOf('update') >= 0; + } + + get hasExtraProps() { + return this.isEdit; + } + + get hasConfirmStep() { + return false; + } + + get params() { + const { id } = this.props.match.params; + return { id }; + } + + async getDetail() { + if (this.isEdit) { + const result = await globalClusterTemplateStore.fetchDetail(this.params); + this.setState({ + extra: toJS(result), + }); + } + } + + get steps() { + return [ + { + title: t("Info *"), + component: StepInfo + }, + { + title: t("Node Spec *"), + component: StepNodeSpec + }, + { + title: t("Network"), + component: StepNetwork + }, + { + title: t("Labels"), + component: StepLabel + } + ] + } + + onSubmit = (values) => { + const requestLabels = {}; + const { additionalLabels } = values; + if (additionalLabels) { + additionalLabels.forEach(item => { + const labelKey = item.value.key.toLowerCase().trim(); + const labelValue = item.value.value.toLowerCase().trim(); + requestLabels[labelKey] = labelValue; + }) + } + + const body = { + labels: { + ...requestLabels, + admission_control_list: 'NodeRestriction,NamespaceLifecycle,LimitRanger,ServiceAccount,ResourceQuota,TaintNodesByCondition,Priority,DefaultTolerationSeconds,DefaultStorageClass,StorageObjectInUseProtection,PersistentVolumeClaimResize,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,RuntimeClass' + }, + fixed_subnet: values.fixedSubnet, + master_flavor_id: values.masterFlavor, + http_proxy: values.HTTPProxy != null ? values.HTTPProxy : null, + https_proxy: values.HTTPSProxy != null ? values.HTTPSProxy : null, + no_proxy: values.noProxy != null ? values.noProxy : null, + keypair_id: values.keypair, + docker_volume_size: values.dockerVolumeSize, + external_network_id: values.externalNetworkID, + image_id: values.image, + volume_driver: values.volumeDriver, + + public: values.cluster_template_public, + hidden: values.cluster_template_hidden, + tls_disabled: values.tls_disabled, + registry_enabled: values.docker_registry_enabled, + master_lb_enabled: values.masterLB, + floating_ip_enabled: values.floatingIP, + + docker_storage_driver: values.dockerStorageDriver, + name: values.clusterTemplateName, + network_driver: values.networkDriver, + fixed_network: values.fixedNetwork, + coe: values.coe, + flavor_id: values.flavor, + dns_nameserver: values.DNS, + } + + if (this.isEdit) { + return this.store.update({ id: this.params.id }, body); + } else { + return this.store.create(body); + } + } +} + +export default inject("rootStore")(observer(StepCreate)) \ No newline at end of file diff --git a/src/pages/container-infra/containers/ClusterTemplates/actions/index.jsx b/src/pages/container-infra/containers/ClusterTemplates/actions/index.jsx new file mode 100644 index 00000000..ee7981c5 --- /dev/null +++ b/src/pages/container-infra/containers/ClusterTemplates/actions/index.jsx @@ -0,0 +1,27 @@ +// 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 StepCreate from './StepCreate'; +import DeleteClusterTemplates from './Delete'; +import EditClusterTemplateStep from './EditStep'; + +const actionConfigs = { + rowActions: { + firstAction: DeleteClusterTemplates, + moreActions: [ + { action: EditClusterTemplateStep }, + ], + }, + batchActions: [DeleteClusterTemplates], + primaryActions: [StepCreate], +}; + +export default actionConfigs; diff --git a/src/pages/container-infra/containers/ClusterTemplates/index.jsx b/src/pages/container-infra/containers/ClusterTemplates/index.jsx new file mode 100644 index 00000000..6992ce43 --- /dev/null +++ b/src/pages/container-infra/containers/ClusterTemplates/index.jsx @@ -0,0 +1,65 @@ +// 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 Base from 'containers/List'; +import { inject, observer } from 'mobx-react'; +import globalClusterTemplateStore from 'src/stores/magnum/clusterTemplates'; +import actionConfigs from './actions'; + +export class ClusterTemplates extends Base { + init() { + this.store = globalClusterTemplateStore; + this.downloadStore = globalClusterTemplateStore; + } + + get name() { + return t('clustertemplates'); + } + + get policy() { + return 'container-infra:clustertemplate:get_all'; + } + + get actionConfigs() { + return actionConfigs; + } + + getColumns = () => [ + { + title: t('ID'), + dataIndex: 'uuid', + render: (data) => { + return this.getLinkRender("containerInfraClusterTemplateDetail", data, { id: data }) + }, + }, + { + title: t('COE'), + isHideable: true, + dataIndex: 'coe', + }, + { + title: t('Network Driver'), + isHideable: true, + dataIndex: 'network_driver', + }, + { + title: t('Keypair'), + isHideable: true, + dataIndex: 'keypair_id', + render: (value) => { + return this.getLinkRender("keypairDetail", value, { id: value }) + } + }, + ]; +} + +export default inject("rootStore")(observer(ClusterTemplates)) \ No newline at end of file diff --git a/src/pages/container-infra/containers/Clusters/Detail/BaseDetail.jsx b/src/pages/container-infra/containers/Clusters/Detail/BaseDetail.jsx new file mode 100644 index 00000000..f2d7db71 --- /dev/null +++ b/src/pages/container-infra/containers/Clusters/Detail/BaseDetail.jsx @@ -0,0 +1,181 @@ +// Copyright 2021 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 Base from 'containers/BaseDetail'; +import { inject, observer } from 'mobx-react'; +import React from 'react'; + +export class BaseDetail extends Base { + + get leftCards() { + return [this.baseInfoCard, this.miscellaneousCard]; + } + + get rightCards() { + return [this.nodesCard, this.labelCard, this.stackCard]; + } + + get baseInfoCard() { + const options = [ + { + label: t('Name'), + dataIndex: "template.name", + render: (value) => { + return this.getLinkRender('containerInfraClusterTemplateDetail', value, { id: this.detailData.template.uuid }) + }, + }, + { + label: t("ID"), + dataIndex: "template.uuid" + }, + { + label: t("COE"), + dataIndex: "template.coe", + }, + { + label: t("Image ID"), + dataIndex: "template.image_id", + } + ]; + + return { + title: t('Cluster Template'), + options, + }; + } + + get miscellaneousCard() { + const options = [ + { + label: t('Discovery URL'), + dataIndex: 'discovery_url', + }, + { + label: t('Cluster Create Timeout'), + dataIndex: 'create_timeout', + }, + { + label: t('Keypair'), + dataIndex: 'keypair', + }, + { + label: t('Docker Volume Size'), + dataIndex: 'docker_volume_size', + }, + { + label: t('Master Flavor ID'), + dataIndex: 'master_flavor_id', + }, + { + label: t('Node Flavor ID'), + dataIndex: 'flavor_id', + }, + { + label: t('COE Version'), + dataIndex: 'coe_version', + }, + { + label: t('Container Version'), + dataIndex: 'container_version', + }, + ]; + + return { + title: t('Miscellaneous'), + options, + }; + } + + get nodesCard() { + const options = [ + { + label: t('Master Count'), + dataIndex: 'master_count', + }, + { + label: t('Node Count'), + dataIndex: 'node_count', + }, + { + label: t('API Address'), + dataIndex: 'api_address', + }, + { + label: t('Master Addresses'), + dataIndex: 'master_addresses', + }, + { + label: t('Node Addresses'), + dataIndex: 'node_addresses', + }, + ]; + + return { + title: t('Nodes'), + labelCol: 3, + options, + }; + } + + get labelCard() { + const options = [ + { + label: t('Labels'), + dataIndex: 'labels', + render: (value) => + Object.entries(value).map(([key, val]) => { + return ( + +
    +
  • + {key} : {val} +
  • +
+
+ ); + }), + }, + ]; + + return { + title: t('Labels'), + labelCol: 2, + options, + }; + } + + get stackCard() { + const options = [ + { + label: t('Stack ID'), + dataIndex: 'stack_id', + }, + { + label: t('Stack Faults'), + dataIndex: 'faults', + render: (value) => value ? Object.entries(value).map(([key, val]) => { + return
  • {key} : {val}
+ }) : " - " + }, + ]; + + return { + title: t('Stack'), + labelCol: 2, + options, + }; + } +} + +export default inject("rootStore")(observer(BaseDetail)) \ No newline at end of file diff --git a/src/pages/container-infra/containers/Clusters/Detail/index.jsx b/src/pages/container-infra/containers/Clusters/Detail/index.jsx new file mode 100644 index 00000000..c90069f3 --- /dev/null +++ b/src/pages/container-infra/containers/Clusters/Detail/index.jsx @@ -0,0 +1,84 @@ +// Copyright 2021 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 BaseDetail from './BaseDetail'; +import globalClustersStore from 'src/stores/magnum/clusters'; + +export class ClustersDetail extends Base { + init() { + this.store = globalClustersStore; + } + + get name() { + return t('Cluster Detail'); + } + + get listUrl() { + return this.getRoutePath('containerInfraClusters'); + } + + get policy() { + return 'container-infra:cluster:detail'; + } + + get detailInfos() { + return [ + { + title: t('Name'), + dataIndex: 'name', + }, + { + title: t('Created'), + dataIndex: 'created_at', + valueRender: 'toLocalTime', + }, + { + title: t('Updated'), + dataIndex: 'updated_at', + valueRender: 'toLocalTime', + }, + { + title: t('Status'), + dataIndex: 'status', + }, + { + title: t('Status Reason'), + dataIndex: 'status_reason', + }, + { + title: t('Health Status'), + dataIndex: 'health_status', + }, + { + title: t('Health Status Reason'), + dataIndex: 'health_status_reason', + render: (value) => (value.length > 0 ? value : '-'), + }, + ]; + } + + get tabs() { + return [ + { + title: t('General Info'), + key: 'general_info', + component: BaseDetail, + }, + ]; + } +} + +export default inject("rootStore")(observer(ClustersDetail)) \ No newline at end of file diff --git a/src/pages/container-infra/containers/Clusters/actions/Delete.jsx b/src/pages/container-infra/containers/Clusters/actions/Delete.jsx new file mode 100644 index 00000000..5b58540e --- /dev/null +++ b/src/pages/container-infra/containers/Clusters/actions/Delete.jsx @@ -0,0 +1,44 @@ +// Copyright 2021 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 { ConfirmAction } from 'containers/Action'; +import globalClustersStore from 'stores/magnum/clusters'; + +export default class DeleteClusters extends ConfirmAction { + get id() { + return 'delete'; + } + + get title() { + return t('Delete Clusters') + } + + get actionName() { + return t('Delete Clusters'); + } + + get buttonText() { + return t('Delete'); + } + + get buttonType() { + return 'danger'; + } + + policy = 'container-infra:cluster:delete'; + + allowedCheckFunc = () => true; + + onSubmit = (data) => globalClustersStore.delete({ id: data.uuid }); +} diff --git a/src/pages/container-infra/containers/Clusters/actions/Resize.jsx b/src/pages/container-infra/containers/Clusters/actions/Resize.jsx new file mode 100644 index 00000000..7d6e06d9 --- /dev/null +++ b/src/pages/container-infra/containers/Clusters/actions/Resize.jsx @@ -0,0 +1,60 @@ +// 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 { ModalAction } from 'src/containers/Action'; +import globalClustersStore from 'stores/magnum/clusters'; + +export class ResizeClusters extends ModalAction { + init() { + this.store = globalClustersStore; + } + + static id = 'resize-cluster'; + + static title = t('Resize Cluster'); + + policy = 'container-infra:cluster:resize'; + + static allowed() { + return Promise.resolve(true); + } + + get name() { + return t('Resize Cluster'); + } + + get buttonText() { + return t('Resize'); + } + + get formItems() { + return [ + { + name: 'node_count', + label: t('Instance'), + type: 'input-number', + min: 1, + required: true, + }, + ]; + } + + onSubmit = (data) => { + this.store.update( + { id: this.props.item.uuid }, + { node_count: data.node_count } + ); + }; +} + +export default inject("rootStore")(observer(ResizeClusters)) diff --git a/src/pages/container-infra/containers/Clusters/actions/StepCreate/StepAdvanced/index.jsx b/src/pages/container-infra/containers/Clusters/actions/StepCreate/StepAdvanced/index.jsx new file mode 100644 index 00000000..24419615 --- /dev/null +++ b/src/pages/container-infra/containers/Clusters/actions/StepCreate/StepAdvanced/index.jsx @@ -0,0 +1,42 @@ +// Copyright 2021 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 Base from "components/Form" +import { inject, observer } from "mobx-react"; +import KeyValueInput from 'components/FormItem/KeyValueInput'; + +export class StepAdvanced extends Base { + + get title() { + return t("Cluster Advanced") + } + + get name() { + return t("Cluster Advanced") + } + + get formItems() { + return [ + { + name: 'additionalLabels', + label: t('Additional Labels'), + type: 'add-select', + itemComponent: KeyValueInput, + addText: t('Add Label'), + } + ] + } +} + +export default inject("rootStore")(observer(StepAdvanced)) \ No newline at end of file diff --git a/src/pages/container-infra/containers/Clusters/actions/StepCreate/StepDetails/index.jsx b/src/pages/container-infra/containers/Clusters/actions/StepCreate/StepDetails/index.jsx new file mode 100644 index 00000000..a9d58669 --- /dev/null +++ b/src/pages/container-infra/containers/Clusters/actions/StepCreate/StepDetails/index.jsx @@ -0,0 +1,112 @@ +// Copyright 2021 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 Base from 'components/Form'; +import { inject, observer } from 'mobx-react'; + +import globalClusterTemplateStore from 'stores/magnum/clusterTemplates'; +import globalAvailabilityZoneStore from 'stores/nova/zone'; +import globalKeypairStore from 'stores/nova/keypair'; + +export class StepDetails extends Base { + init() { + this.getClustertemplates(); + this.getAvailableZones(); + this.getKeypairs(); + } + + get title() { + return t('Cluster Detail'); + } + + get name() { + return t('Cluster Detail'); + } + + async getClustertemplates() { + globalClusterTemplateStore.fetchList(); + } + + get getClusterTemplateList() { + return (globalClusterTemplateStore.list.data || []).map((it) => ({ + value: it.uuid, + label: it.name, + })); + } + + async getAvailableZones() { + globalAvailabilityZoneStore.fetchListWithoutDetail(); + } + + get getAvailableZonesList() { + return (globalAvailabilityZoneStore.list.data || []) + .filter((it) => it.zoneState.available) + .map((it) => ({ + value: it.zoneName, + label: it.zoneName, + })); + } + + async getKeypairs() { + globalKeypairStore.fetchList(); + } + + get getKeypairList() { + return (globalKeypairStore.list.data || []).map((it) => ({ + value: it.name, + label: it.name, + })); + } + + get formItems() { + return [ + { + name: "clusterName", + label: t("Cluster Name"), + type: "input", + required: true + }, + { + name: "clusterTemplateId", + label: t("Cluster Template"), + type: "select", + placeholder: t('Please select'), + options: this.getClusterTemplateList, + allowClear: true, + showSearch: true, + required: true + }, + { + name: "availabilityZone", + label: t("Availability Zone"), + type: "select", + options: this.getAvailableZonesList, + allowClear: true, + showSearch: true, + required: true + }, + { + name: "keypair", + label: t("Keypair"), + type: "select", + options: this.getKeypairList, + allowClear: true, + showSearch: true, + required: true + } + ] + } +} + +export default inject("rootStore")(observer(StepDetails)) \ No newline at end of file diff --git a/src/pages/container-infra/containers/Clusters/actions/StepCreate/StepManagement/index.jsx b/src/pages/container-infra/containers/Clusters/actions/StepCreate/StepManagement/index.jsx new file mode 100644 index 00000000..90acc93d --- /dev/null +++ b/src/pages/container-infra/containers/Clusters/actions/StepCreate/StepManagement/index.jsx @@ -0,0 +1,50 @@ +// Copyright 2021 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 Base from "components/Form"; + +import { inject, observer } from "mobx-react"; + +export class StepManagement extends Base { + + get title() { + return t("Cluster Management") + } + + get name() { + return t("Cluster Management") + } + + get formItems() { + return [ + { + name: "auto_healing_enabled", + label: t("Auto Healing"), + type: "check", + content: t("Automatically repair unhealhty nodes") + }, + { + type: "divider" + }, + { + name: "auto_scaling_enabled", + label: t("Auto Scaling"), + type: "check", + content: t("Auto scaling feature will be enabled") + } + ] + } +} + +export default inject("rootStore")(observer(StepManagement)) \ No newline at end of file diff --git a/src/pages/container-infra/containers/Clusters/actions/StepCreate/StepNetworks/index.jsx b/src/pages/container-infra/containers/Clusters/actions/StepCreate/StepNetworks/index.jsx new file mode 100644 index 00000000..a2f55f4b --- /dev/null +++ b/src/pages/container-infra/containers/Clusters/actions/StepCreate/StepNetworks/index.jsx @@ -0,0 +1,124 @@ +// Copyright 2021 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 Base from "components/Form"; +import { inject, observer } from "mobx-react"; +import globalNetworkStore from "src/stores/neutron/network"; + +export class StepNetworks extends Base { + + init() { + this.getNetworks(); + } + + get title() { + return t("Cluster Network") + } + + get name() { + return t("Cluster Network") + } + + allowed = () => Promise.resolve(); + + get defaultValue() { + return { + enableNetwork: true + }; + } + + get nameForStateUpdate() { + return ['enableNetwork'] + } + + async getNetworks() { + globalNetworkStore.fetchList(); + } + + get getNetworkList() { + return (globalNetworkStore.list.data || []).map((it) => ({ + value: it.id, + label: it.name, + })); + } + + get formItems() { + + const { enableNetwork } = this.state; + + return [ + { + name: "enableLoadBalancer", + label: t("Enable Load Balancer"), + type: "check", + content: t("Enabled Load Balancer for Master Nodes"), + }, + { + name: "enableNetwork", + label: t("Enabled Network"), + type: "check", + content: t("Create New Network"), + }, + { + name: "network", + label: t("Use an Existing Network"), + type: 'network-select-table', + hidden: enableNetwork + }, + { + type: "divider" + }, + { + name: "floating_ip_enabled", + label: t("Cluster API"), + type: "select", + options: [ + { + label: t("Accessible on private network only"), + value: "networkOnly" + }, + { + label: t("Accessible on the public internet"), + value: "publicInternet" + } + ], + }, + { + type: "divider" + }, + { + name: "ingress_controller", + label: t("Ingress Controller"), + type: "select", + options: [ + { + label: t("octavia"), + value: "octavia" + }, + { + label: t("nginx"), + value: "nginx" + }, + { + label: t("traefik"), + value: "traefik" + } + ], + placeholder: t("Choose an ingress controller"), + } + ] + } +} + +export default inject("rootStore")(observer(StepNetworks)) \ No newline at end of file diff --git a/src/pages/container-infra/containers/Clusters/actions/StepCreate/StepSize/index.jsx b/src/pages/container-infra/containers/Clusters/actions/StepCreate/StepSize/index.jsx new file mode 100644 index 00000000..4c47626f --- /dev/null +++ b/src/pages/container-infra/containers/Clusters/actions/StepCreate/StepSize/index.jsx @@ -0,0 +1,78 @@ +// Copyright 2021 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 "components/Form"; +import FlavorSelectTable from 'src/pages/compute/containers/Instance/components/FlavorSelectTable'; + +export class StepSize extends Base { + get title() { + return t("Cluster Size") + } + + get name() { + return t("Cluster Size") + } + + allowed = () => Promise.resolve(); + + getFlavorComponent() { + return ; + } + + onFlavorChange = (value) => { + this.updateContext({ + flavor: value, + }); + }; + + get formItems() { + return [ + { + name: "numberOfMasterNodes", + label: t("Number of Master Nodes"), + type: "input-number", + min: 1, + required: true + }, + { + name: "flavorOfMasterNodes", + label: t("Flavor of Master Nodes"), + type: 'select-table', + component: this.getFlavorComponent(), + required: true, + }, + { + type: "divider" + }, + { + name: "numberOfWorkerNodes", + label: t("Number of Worker Nodes"), + type: "input-number", + min: 1, + required: true, + }, + { + name: "flavorOfWorkerNodes", + label: t("Flavor of Worker Nodes"), + type: 'select-table', + component: this.getFlavorComponent(), + required: true + } + ] + } +} + +export default inject('rootStore')(observer(StepSize)); \ No newline at end of file diff --git a/src/pages/container-infra/containers/Clusters/actions/StepCreate/index.jsx b/src/pages/container-infra/containers/Clusters/actions/StepCreate/index.jsx new file mode 100644 index 00000000..3241b07a --- /dev/null +++ b/src/pages/container-infra/containers/Clusters/actions/StepCreate/index.jsx @@ -0,0 +1,116 @@ +// 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 StepDetails from "./StepDetails"; +import StepSize from './StepSize'; +import StepNetworks from './StepNetworks'; +import StepManagement from './StepManagement'; +import StepAdvanced from './StepAdvanced'; +import { inject, observer } from "mobx-react"; +import { StepAction } from "src/containers/Action"; +import globalClustersStore from "src/stores/magnum/clusters"; + +export class StepCreate extends StepAction { + init() { + this.store = globalClustersStore + } + + static id = "create-cluster"; + + static title = t("Create Cluster"); + + static path = "/container-infra/clusters/create"; + + static policy = "container-infra:cluster:create"; + + static allowed() { + return Promise.resolve(true); + } + + get name() { + return t("Create Instance"); + } + + get listUrl() { + return this.getRoutePath("containerInfraClusters"); + } + + get hasConfirmStep() { + return false; + } + + get steps() { + return [ + { + title: t("Details *"), + component: StepDetails + }, + { + title: t("Size: *"), + component: StepSize + }, + { + title: t("Networks"), + component: StepNetworks + }, + { + title: t("Management"), + component: StepManagement + }, + { + title: t("Advanced"), + component: StepAdvanced + } + ] + } + + onSubmit = (values) => { + + const { additionalLabels, auto_healing_enabled, auto_scaling_enabled } = values; + const requestLabels = {}; + + if (additionalLabels) { + additionalLabels.forEach((item) => { + const labelKey = item.value.key.toLowerCase().trim(); + const labelValue = item.value.value.toLowerCase().trim(); + requestLabels[labelKey] = labelValue; + }) + } + + const data = { + name: values.clusterName, + labels: { + ...requestLabels, + auto_healing_enabled: auto_healing_enabled, + auto_scaling_enabled: auto_scaling_enabled, + admission_control_list: 'NodeRestriction,NamespaceLifecycle,LimitRanger,ServiceAccount,ResourceQuota,TaintNodesByCondition,Priority,DefaultTolerationSeconds,DefaultStorageClass,StorageObjectInUseProtection,PersistentVolumeClaimResize,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,RuntimeClass' + }, + cluster_template_id: values.clusterTemplateId, + create_timeout: 60, + master_count: values.numberOfMasterNodes, + node_count: values.numberOfWorkerNodes, + keypair: values.keypair, + master_flavor_id: values.flavorOfMasterNodes, + flavor_id: values.flavorOfWorkerNodes, + master_lb_enabled: values.enableLoadBalancer, + floating_ip_enabled: values.floating_ip_enabled === 'networkOnly' ? false : true, + }; + + if (!values.enableNetwork) { + data.fixed_network = values.network; + } + + return this.store.create(data); + } +} + +export default inject("rootStore")(observer(StepCreate)) \ No newline at end of file diff --git a/src/pages/container-infra/containers/Clusters/actions/index.jsx b/src/pages/container-infra/containers/Clusters/actions/index.jsx new file mode 100644 index 00000000..b803e513 --- /dev/null +++ b/src/pages/container-infra/containers/Clusters/actions/index.jsx @@ -0,0 +1,25 @@ +// 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 DeleteClusters from './Delete'; +import ResizeClusters from './Resize'; +import StepCreate from './StepCreate'; + +const actionConfigs = { + rowActions: { + firstAction: DeleteClusters, + moreActions: [{ action: ResizeClusters }], + }, + batchActions: [DeleteClusters], + primaryActions: [StepCreate], +}; + +export default actionConfigs; diff --git a/src/pages/container-infra/containers/Clusters/index.jsx b/src/pages/container-infra/containers/Clusters/index.jsx new file mode 100644 index 00000000..19456546 --- /dev/null +++ b/src/pages/container-infra/containers/Clusters/index.jsx @@ -0,0 +1,66 @@ +// Copyright 2021 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 Base from 'containers/List'; +import { inject, observer } from 'mobx-react'; +import globalClustersStore from 'src/stores/magnum/clusters'; +import actionConfigs from './actions'; + +export class Clusters extends Base { + init() { + this.store = globalClustersStore; + this.downloadStore = globalClustersStore; + } + + get name() { + return t('clusters'); + } + + get policy() { + return 'container-infra:cluster:get_all'; + } + + get actionConfigs() { + return actionConfigs; + } + + get rowKey() { + return 'uuid'; + } + + getColumns = () => [ + { + title: t('ID/Name'), + dataIndex: 'name', + routeName: this.getRouteName('containerInfraClusterDetail'), + }, + { + title: t('Status'), + isHideable: true, + dataIndex: 'status', + }, + { + title: t('Health Status'), + isHideable: true, + dataIndex: 'health_status', + }, + { + title: t('Keypair'), + isHideable: true, + dataIndex: 'keypair', + }, + ]; +} + +export default inject("rootStore")(observer(Clusters)) \ No newline at end of file diff --git a/src/pages/container-infra/routes/index.js b/src/pages/container-infra/routes/index.js new file mode 100644 index 00000000..0520d457 --- /dev/null +++ b/src/pages/container-infra/routes/index.js @@ -0,0 +1,49 @@ +// Copyright 2021 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 BaseLayout from 'layouts/Basic'; +import E404 from 'pages/base/containers/404'; +import Clusters from '../containers/Clusters'; +import ClustersDetail from '../containers/Clusters/Detail'; +import ClusterTemplates from '../containers/ClusterTemplates'; +import ClusterTemplateDetail from '../containers/ClusterTemplates/Detail'; +import ClustersCreate from '../containers/Clusters/actions/StepCreate'; +import ClustersTemplateCreate from '../containers/ClusterTemplates/actions/StepCreate' +import StepUpdateClusterTemplate from '../containers/ClusterTemplates/actions/EditStep' + +const PATH = '/container-infra'; +export default [ + { + path: PATH, + component: BaseLayout, + routes: [ + { path: `${PATH}/clusters`, component: Clusters, exact: true }, + { path: `${PATH}/clusters/detail/:id`, component: ClustersDetail, exact: true }, + { path: `${PATH}/cluster-template`, component: ClusterTemplates, exact: true }, + { path: `${PATH}/cluster-template/detail/:id`, component: ClusterTemplateDetail, exact: true }, + { + path: `${PATH}/clusters/create`, + component: ClustersCreate, + exact: true, + }, + { + path: `${PATH}/clusters-template/create`, + component: ClustersTemplateCreate, + exact: true, + }, + { path: `${PATH}/cluster-template/update/:id`, component: StepUpdateClusterTemplate, exact: true }, + { path: '*', component: E404 }, + ], + }, +]; diff --git a/src/stores/magnum/clusterTemplates.js b/src/stores/magnum/clusterTemplates.js new file mode 100644 index 00000000..0a4adc57 --- /dev/null +++ b/src/stores/magnum/clusterTemplates.js @@ -0,0 +1,51 @@ +// Copyright 2021 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 Base from 'stores/base'; +import client from 'client'; +import { action } from 'mobx'; + +export class ClusterTemplatesStore extends Base { + get client() { + return client.magnum.clusterTemplates; + } + + @action + async create(newbody) { + return this.submitting(this.client.create(newbody)); + } + + @action + async delete({ id }) { + return this.client.delete(id); + } + + @action + async get(data) { + return this.client.get(data); + } + + @action + async update({ id }, newbody) { + return this.client.update(id, newbody); + } + + async listDidFetch(items) { + if (!items.length) return items + return items.map(it => ({ ...it, id: it.uuid })); + } +} + +const globalClusterTemplateStore = new ClusterTemplatesStore(); +export default globalClusterTemplateStore; diff --git a/src/stores/magnum/clusters.js b/src/stores/magnum/clusters.js new file mode 100644 index 00000000..5e62d6f9 --- /dev/null +++ b/src/stores/magnum/clusters.js @@ -0,0 +1,62 @@ +// Copyright 2021 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 Base from 'stores/base'; +import client from 'client'; +import { action } from 'mobx'; + +export class ClustersStore extends Base { + + get client() { + return client.magnum.clusters; + } + + get templateClient() { + return client.magnum.clusterTemplates; + } + + get listWithDetail() { + return true; + } + + @action + async create(newbody) { + return this.submitting(this.client.create(newbody)); + } + + @action + async delete({ id }) { + return this.client.delete(id); + } + + @action + async update({ id }, newbody) { + return this.client.resize.create(id, newbody); + } + + async detailDidFetch(item) { + const { cluster_template_id } = item || {}; + const template = await this.templateClient.show(cluster_template_id); + item.template = template; + return item; + } + + async listDidFetch(items) { + if (!items.length) return items + return items.map(it => ({ ...it, id: it.uuid })); + } +} + +const globalClustersStore = new ClustersStore(); +export default globalClustersStore;