From f3c284a8357e5f8ce5e96a2a8deb4512bca1aee7 Mon Sep 17 00:00:00 2001 From: "Jingwei.Zhang" Date: Wed, 20 Apr 2022 18:24:17 +0800 Subject: [PATCH] feat: Support manila share type page 1. Add share type page 2. Add create share type 3. Add update share type 4. Add manage access 5. Add delete share type 6. Add share type detail page 7. Add create extra spec 8. Add update extra spec 9. Add delete extra spec Change-Id: I2d1497b8716b178693e127ae7350e9946a479817 --- src/client/client/constants.js | 9 +- src/client/index.js | 4 +- src/client/manila/index.js | 73 ++++++++ .../FormItem/KeyValueInput/index.jsx | 8 +- src/layouts/admin-menu.jsx | 25 +++ src/locales/en.json | 12 +- src/locales/zh.json | 12 +- src/pages/basic/routes/index.js | 9 +- src/pages/share/App.jsx | 21 +++ .../Detail/ExtraSpec/actions/Create.jsx | 71 ++++++++ .../Detail/ExtraSpec/actions/Delete.jsx | 52 ++++++ .../Detail/ExtraSpec/actions/Edit.jsx | 77 +++++++++ .../Detail/ExtraSpec/actions/index.jsx | 32 ++++ .../ShareType/Detail/ExtraSpec/index.jsx | 58 +++++++ .../containers/ShareType/Detail/index.jsx | 72 ++++++++ .../containers/ShareType/actions/Create.jsx | 162 ++++++++++++++++++ .../containers/ShareType/actions/Delete.jsx | 42 +++++ .../containers/ShareType/actions/Edit.jsx | 66 +++++++ .../ShareType/actions/ManageAccess.jsx | 140 +++++++++++++++ .../containers/ShareType/actions/index.jsx | 36 ++++ .../share/containers/ShareType/index.jsx | 68 ++++++++ src/pages/share/routes/index.js | 35 ++++ src/resources/share-type.js | 4 + src/stores/manila/extra-spec.js | 58 +++++++ src/stores/manila/share-type.js | 113 ++++++++++++ 25 files changed, 1251 insertions(+), 8 deletions(-) create mode 100644 src/client/manila/index.js create mode 100644 src/pages/share/App.jsx create mode 100644 src/pages/share/containers/ShareType/Detail/ExtraSpec/actions/Create.jsx create mode 100644 src/pages/share/containers/ShareType/Detail/ExtraSpec/actions/Delete.jsx create mode 100644 src/pages/share/containers/ShareType/Detail/ExtraSpec/actions/Edit.jsx create mode 100644 src/pages/share/containers/ShareType/Detail/ExtraSpec/actions/index.jsx create mode 100644 src/pages/share/containers/ShareType/Detail/ExtraSpec/index.jsx create mode 100644 src/pages/share/containers/ShareType/Detail/index.jsx create mode 100644 src/pages/share/containers/ShareType/actions/Create.jsx create mode 100644 src/pages/share/containers/ShareType/actions/Delete.jsx create mode 100644 src/pages/share/containers/ShareType/actions/Edit.jsx create mode 100644 src/pages/share/containers/ShareType/actions/ManageAccess.jsx create mode 100644 src/pages/share/containers/ShareType/actions/index.jsx create mode 100644 src/pages/share/containers/ShareType/index.jsx create mode 100644 src/pages/share/routes/index.js create mode 100644 src/resources/share-type.js create mode 100644 src/stores/manila/extra-spec.js create mode 100644 src/stores/manila/share-type.js diff --git a/src/client/client/constants.js b/src/client/client/constants.js index f7cfa343..48838ef5 100644 --- a/src/client/client/constants.js +++ b/src/client/client/constants.js @@ -30,7 +30,8 @@ export const endpointVersionMap = { heat: 'v1', octavia: 'v2', swift: 'v1', - trove: 'v1.0' + trove: 'v1.0', + manilav2: 'v2', }; export const endpointsDefault = { @@ -67,6 +68,7 @@ export const heatBase = () => getOpenstackEndpoint('heat'); export const octaviaBase = () => getOpenstackEndpoint('octavia'); export const swiftBase = () => getOpenstackEndpoint('swift'); export const troveBase = () => getOpenstackEndpoint('trove'); +export const manilaBase = () => getOpenstackEndpoint('manilav2'); export const ironicOriginEndpoint = () => getOriginEndpoint('ironic'); export const vpnEndpoint = () => getOriginEndpoint('neutron_vpn'); @@ -74,6 +76,7 @@ export const lbEndpoint = () => getOriginEndpoint('octavia'); export const qosEndpoint = () => getOriginEndpoint('neutron_qos'); export const swiftEndpoint = () => getOriginEndpoint('swift'); export const cinderEndpoint = () => getOriginEndpoint('cinder'); +export const manilaEndpoint = () => getOriginEndpoint('manilav2'); export const apiVersionMaps = { nova: { @@ -96,6 +99,10 @@ export const apiVersionMaps = { key: 'X-OpenStack-Ironic-Inspector-API-Version', value: '1.15', }, + manila: { + key: 'X-OpenStack-Manila-API-Version', + value: '2.51', + }, }; export const getOpenstackApiVersion = (url) => { diff --git a/src/client/index.js b/src/client/index.js index 3944f0f2..3478bd43 100644 --- a/src/client/index.js +++ b/src/client/index.js @@ -24,6 +24,7 @@ import placement from './placement'; import ironic from './ironic'; import swift from './swift'; import trove from './trove'; +import manila from './manila'; const client = { skyline, @@ -37,7 +38,8 @@ const client = { placement, ironic, swift, - trove + trove, + manila, }; window.client = client; diff --git a/src/client/manila/index.js b/src/client/manila/index.js new file mode 100644 index 00000000..107dd5cd --- /dev/null +++ b/src/client/manila/index.js @@ -0,0 +1,73 @@ +// 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 '../client/base'; +import { manilaBase, manilaEndpoint } from '../client/constants'; + +class ManilaClient extends Base { + get baseUrl() { + return manilaBase(); + } + + get enable() { + return !!manilaEndpoint(); + } + + get projectInUrl() { + return true; + } + + get resources() { + return [ + { + key: 'shares', + responseKey: 'share', + extendOperations: [ + { + key: 'detail', + method: 'get', + isDetail: false, + }, + ], + }, + { + key: 'types', + responseKey: 'share_type', + extendOperations: [ + { + key: 'action', + method: 'post', + }, + { + name: 'getAccess', + key: 'share_type_access', + }, + { + key: 'default', + }, + ], + subResources: [ + { + name: 'extraSpecs', + key: 'extra_specs', + responseKey: 'extra_spec', + }, + ], + }, + ]; + } +} + +const manilaClient = new ManilaClient(); +export default manilaClient; diff --git a/src/components/FormItem/KeyValueInput/index.jsx b/src/components/FormItem/KeyValueInput/index.jsx index dde6f706..c019a9a4 100644 --- a/src/components/FormItem/KeyValueInput/index.jsx +++ b/src/components/FormItem/KeyValueInput/index.jsx @@ -24,6 +24,8 @@ export default class index extends Component { value: PropTypes.object, keyReadonly: PropTypes.bool, valueReadonly: PropTypes.bool, + keySpan: PropTypes.number, + valueSpan: PropTypes.number, }; static defaultProps = { @@ -78,10 +80,10 @@ export default class index extends Component { render() { const { key, value } = this.state; - const { keyReadonly, valueReadonly } = this.props; + const { keyReadonly, valueReadonly, keySpan, valueSpan } = this.props; return ( - + - + { @@ -398,6 +399,30 @@ const renderMenu = (t) => { }, ], }, + { + path: '/share', + name: t('Share File Storage'), + key: 'fileStorageAdmin', + icon: , + children: [ + { + path: '/share/share-type-admin', + name: t('Share Type'), + key: 'shareTypeAdmin', + level: 1, + endpoints: 'manilav2', + children: [ + { + path: /^\/share\/share-type-admin\/detail\/.[^/]+$/, + name: t('Share Type Detail'), + key: 'shareTypeDetailAdmin', + level: 2, + routePath: '/share/share-type-admin/detail/:id', + }, + ], + }, + ], + }, { path: '/identity', name: t('Identity'), diff --git a/src/locales/en.json b/src/locales/en.json index a184c899..fba89b9e 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -48,6 +48,7 @@ "Add Data Disks": "Add Data Disks", "Add External Members": "Add External Members", "Add Extra Info": "Add Extra Info", + "Add Extra Spec": "Add Extra Spec", "Add IP": "Add IP", "Add Member": "Add Member", "Add NUMA Node": "Add NUMA Node", @@ -411,6 +412,7 @@ "Create Rule": "Create Rule", "Create Security Group": "Create Security Group", "Create Server Group": "Create Server Group", + "Create Share Type": "Create Share Type", "Create Snapshot": "Create Snapshot", "Create Stack": "Create Stack", "Create Static Route": "Create Static Route", @@ -552,6 +554,7 @@ "Delete Rule": "Delete Rule", "Delete Security Group": "Delete Security Group", "Delete Server Group": "Delete Server Group", + "Delete Share Type": "Delete Share Type", "Delete Snapshot": "Delete Snapshot", "Delete Static Route": "Delete Static Route", "Delete Subnet": "Delete Subnet", @@ -635,6 +638,7 @@ "Downloading": "Downloading", "Draining": "Draining", "Driver": "Driver", + "Driver Handles Share Servers": "Driver Handles Share Servers", "Driver Info": "Driver Info", "Driver Interface": "Driver Interface", "Duplicate tag name: {tag}": "Duplicate tag name: {tag}", @@ -752,6 +756,7 @@ "External Port": "External Port", "Extra Infos": "Extra Infos", "Extra Spec": "Extra Spec", + "Extra Specs": "Extra Specs", "FAKE": "FAKE", "FLAT": "FLAT", "Fail Rollback": "Fail Rollback", @@ -767,7 +772,6 @@ "Filename": "Filename", "Files: {names}": "Files: {names}", "Fill In The Parameters": "Fill In The Parameters", - "Filter Project": "Filter Project", "Fingerprint": "Fingerprint", "Finish Resize": "Finish Resize", "Finland": "Finland", @@ -1744,6 +1748,9 @@ "Set Boot Device": "Set Boot Device", "Set IP": "Set IP", "Seychelles": "Seychelles", + "Share File Storage": "Share File Storage", + "Share Type": "Share Type", + "Share Type Detail": "Share Type Detail", "Shared": "Shared", "Shared Image": "Shared Image", "Shared Network": "Shared Network", @@ -2243,6 +2250,7 @@ "create ipsec site connection": "create ipsec site connection", "create network": "create network", "create router": "create router", + "create share type": "create share type", "create snapshot": "create snapshot", "create stack": "create stack", "create volume": "create volume", @@ -2391,6 +2399,8 @@ "server groups": "server groups", "services": "services", "settings": "settings", + "share type": "share type", + "share types": "share types", "shelve instance": "shelve instance", "snapshot": "snapshot", "soft reboot instance": "soft reboot instance", diff --git a/src/locales/zh.json b/src/locales/zh.json index c6cc80cf..2bde9417 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -48,6 +48,7 @@ "Add Data Disks": "添加数据盘", "Add External Members": "添加外部成员", "Add Extra Info": "添加额外信息", + "Add Extra Spec": "添加额外规格", "Add IP": "增加IP", "Add Member": "添加成员", "Add NUMA Node": "添加NUMA节点", @@ -411,6 +412,7 @@ "Create Rule": "创建规则", "Create Security Group": "创建安全组", "Create Server Group": "创建云主机组", + "Create Share Type": "创建共享类型", "Create Snapshot": "创建快照", "Create Stack": "创建堆栈", "Create Static Route": "创建静态路由", @@ -552,6 +554,7 @@ "Delete Rule": "删除规则", "Delete Security Group": "删除安全组", "Delete Server Group": "删除云主机组", + "Delete Share Type": "删除共享类型", "Delete Snapshot": "删除快照", "Delete Static Route": "删除静态路由", "Delete Subnet": "删除子网", @@ -635,6 +638,7 @@ "Downloading": "下载中", "Draining": "满载", "Driver": "驱动", + "Driver Handles Share Servers": "共享服务器", "Driver Info": "驱动信息", "Driver Interface": "驱动接口", "Duplicate tag name: {tag}": "重复的tag名称:{tag}", @@ -752,6 +756,7 @@ "External Port": "源端口", "Extra Infos": "额外信息", "Extra Spec": "额外规格", + "Extra Specs": "额外规格", "FAKE": "", "FLAT": "", "Fail Rollback": "失败回滚", @@ -767,7 +772,6 @@ "Filename": "文件名", "Files: {names}": "文件:{names}", "Fill In The Parameters": "参数填写", - "Filter Project": "筛选项目", "Fingerprint": "指纹", "Finish Resize": "完成调整", "Finland": "芬兰", @@ -1744,6 +1748,9 @@ "Set Boot Device": "设置引导设备", "Set IP": "设置IP", "Seychelles": "塞舌尔", + "Share File Storage": "文件存储", + "Share Type": "共享类型", + "Share Type Detail": "共享类型详情", "Shared": "共享", "Shared Image": "共享镜像", "Shared Network": "共享网络", @@ -2243,6 +2250,7 @@ "create ipsec site connection": "创建IPsec站点连接", "create network": "创建网络", "create router": "创建路由", + "create share type": "创建共享类型", "create snapshot": "创建快照", "create stack": "创建堆栈", "create volume": "创建云硬盘", @@ -2391,6 +2399,8 @@ "server groups": "云主机组", "services": "服务", "settings": "配置", + "share type": "共享类型", + "share types": "共享类型", "shelve instance": "归档云主机", "snapshot": "快照", "soft reboot instance": "软重启云主机", diff --git a/src/pages/basic/routes/index.js b/src/pages/basic/routes/index.js index 205b2ba0..c01cddf7 100644 --- a/src/pages/basic/routes/index.js +++ b/src/pages/basic/routes/index.js @@ -47,6 +47,9 @@ const MonitorCenter = lazy(() => const Database = lazy(() => import(/* webpackChunkName: "monitor-center" */ 'pages/database/App') ); +const Share = lazy(() => + import(/* webpackChunkName: "share" */ 'pages/share/App') +); const E404 = lazy(() => import(/* webpackChunkName: "E404" */ 'pages/base/containers/404') ); @@ -93,7 +96,11 @@ export default [ }, { path: `/database`, - component: Database + component: Database, + }, + { + path: `/share`, + component: Share, }, { path: '*', component: E404 }, ], diff --git a/src/pages/share/App.jsx b/src/pages/share/App.jsx new file mode 100644 index 00000000..014bcad1 --- /dev/null +++ b/src/pages/share/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/share/containers/ShareType/Detail/ExtraSpec/actions/Create.jsx b/src/pages/share/containers/ShareType/Detail/ExtraSpec/actions/Create.jsx new file mode 100644 index 00000000..8a575bdb --- /dev/null +++ b/src/pages/share/containers/ShareType/Detail/ExtraSpec/actions/Create.jsx @@ -0,0 +1,71 @@ +// 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 { ModalAction } from 'containers/Action'; +import globalExtraSpecStore from 'stores/manila/extra-spec'; + +export class Create extends ModalAction { + static id = 'create'; + + static title = t('Create Extra Specs'); + + get name() { + return t('Create Extra Specs'); + } + + static policy = 'manila:share_types_extra_spec:create'; + + static allowed = () => Promise.resolve(true); + + get defaultValue() { + return {}; + } + + get instanceName() { + return this.values.keyName; + } + + get formItems() { + return [ + { + name: 'keyName', + label: t('Key'), + type: 'input', + required: true, + placeholder: t('Please input key'), + }, + { + name: 'value', + label: t('Value'), + type: 'input', + placeholder: t('Please input value'), + required: true, + }, + ]; + } + + init() { + this.store = globalExtraSpecStore; + } + + onSubmit = (values) => { + const { id } = this.containerProps.detail; + const { keyName, value } = values; + const extra_specs = { [keyName]: value }; + return this.store.createOrUpdate(id, extra_specs); + }; +} + +export default inject('rootStore')(observer(Create)); diff --git a/src/pages/share/containers/ShareType/Detail/ExtraSpec/actions/Delete.jsx b/src/pages/share/containers/ShareType/Detail/ExtraSpec/actions/Delete.jsx new file mode 100644 index 00000000..c5ad4c6f --- /dev/null +++ b/src/pages/share/containers/ShareType/Detail/ExtraSpec/actions/Delete.jsx @@ -0,0 +1,52 @@ +// 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 globalExtraSpecStore from 'stores/manila/extra-spec'; + +export default class Delete extends ConfirmAction { + get id() { + return 'delete'; + } + + get title() { + return t('Delete Extra Specs'); + } + + get buttonType() { + return 'danger'; + } + + get buttonText() { + return t('Delete'); + } + + get actionName() { + return t('Delete Extra Specs'); + } + + policy = 'manila:share_types_extra_spec:delete'; + + allowedCheckFunc = (data) => data.keyName !== 'driver_handles_share_servers'; + + onSubmit = (data) => { + const { id } = this.containerProps.detail; + const { keyName } = data; + const body = { + id, + keyName, + }; + return globalExtraSpecStore.delete(body); + }; +} diff --git a/src/pages/share/containers/ShareType/Detail/ExtraSpec/actions/Edit.jsx b/src/pages/share/containers/ShareType/Detail/ExtraSpec/actions/Edit.jsx new file mode 100644 index 00000000..11dff124 --- /dev/null +++ b/src/pages/share/containers/ShareType/Detail/ExtraSpec/actions/Edit.jsx @@ -0,0 +1,77 @@ +// 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 { ModalAction } from 'containers/Action'; +import globalExtraSpecStore from 'stores/manila/extra-spec'; + +export class Edit extends ModalAction { + static id = 'edit'; + + static title = t('Edit Extra Specs'); + + static buttonText = t('Edit'); + + get name() { + return t('Edit Extra Specs'); + } + + get instanceName() { + return this.item.keyName; + } + + get defaultValue() { + const { keyName, value } = this.item; + const defaultValue = { + keyName, + value, + }; + return defaultValue; + } + + static policy = 'manila:share_types_extra_spec:update'; + + static allowed = () => Promise.resolve(true); + + get formItems() { + return [ + { + name: 'keyName', + label: t('Key'), + type: 'input', + disabled: true, + placeholder: t('Please input key'), + }, + { + name: 'value', + label: t('Value'), + type: 'input', + placeholder: t('Please input value'), + }, + ]; + } + + init() { + this.store = globalExtraSpecStore; + } + + onSubmit = (values) => { + const { id } = this.containerProps.detail; + const { keyName, value } = values; + const extra_specs = { [keyName]: value }; + return this.store.createOrUpdate(id, extra_specs); + }; +} + +export default inject('rootStore')(observer(Edit)); diff --git a/src/pages/share/containers/ShareType/Detail/ExtraSpec/actions/index.jsx b/src/pages/share/containers/ShareType/Detail/ExtraSpec/actions/index.jsx new file mode 100644 index 00000000..19482145 --- /dev/null +++ b/src/pages/share/containers/ShareType/Detail/ExtraSpec/actions/index.jsx @@ -0,0 +1,32 @@ +// 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 Create from './Create'; +import Edit from './Edit'; +import Delete from './Delete'; + +const actionConfigs = { + rowActions: { + firstAction: Delete, + moreActions: [ + { + action: Edit, + }, + ], + }, + batchActions: [Delete], + primaryActions: [Create], +}; + +export default actionConfigs; diff --git a/src/pages/share/containers/ShareType/Detail/ExtraSpec/index.jsx b/src/pages/share/containers/ShareType/Detail/ExtraSpec/index.jsx new file mode 100644 index 00000000..9d1aa094 --- /dev/null +++ b/src/pages/share/containers/ShareType/Detail/ExtraSpec/index.jsx @@ -0,0 +1,58 @@ +// 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 { observer, inject } from 'mobx-react'; +import Base from 'containers/List'; +import { ExtraSpecStore } from 'stores/manila/extra-spec'; +import actionConfigs from './actions'; + +export class ExtraSpecs extends Base { + init() { + this.store = new ExtraSpecStore(); + } + + get policy() { + return 'manila:share_types_extra_spec:index'; + } + + get name() { + return t('extra specs'); + } + + getColumns = () => [ + { + title: t('Key'), + dataIndex: 'keyName', + }, + { + title: t('Value'), + dataIndex: 'value', + }, + ]; + + get actionConfigs() { + return actionConfigs; + } + + get searchFilters() { + return [ + { + label: t('Key'), + name: 'keyName', + }, + ]; + } +} + +export default inject('rootStore')(observer(ExtraSpecs)); diff --git a/src/pages/share/containers/ShareType/Detail/index.jsx b/src/pages/share/containers/ShareType/Detail/index.jsx new file mode 100644 index 00000000..e2f21061 --- /dev/null +++ b/src/pages/share/containers/ShareType/Detail/index.jsx @@ -0,0 +1,72 @@ +// 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 { ShareTypeStore } from 'stores/manila/share-type'; +import Base from 'containers/TabDetail'; +import ExtraSpec from './ExtraSpec'; +import actionConfigs from '../actions'; + +export class Detail extends Base { + get name() { + return t('share type'); + } + + get policy() { + return 'manila:share_type:show'; + } + + get listUrl() { + return this.getRoutePath('shareType'); + } + + get actionConfigs() { + return actionConfigs; + } + + get detailInfos() { + return [ + { + title: t('Name'), + dataIndex: 'name', + }, + { + title: t('Description'), + dataIndex: 'description', + }, + { + title: t('Public'), + dataIndex: 'is_public', + isHideable: true, + valueRender: 'yesNo', + }, + ]; + } + + get tabs() { + return [ + { + title: t('Extra Spec'), + key: 'ExtraSpec', + component: ExtraSpec, + }, + ]; + } + + init() { + this.store = new ShareTypeStore(); + } +} + +export default inject('rootStore')(observer(Detail)); diff --git a/src/pages/share/containers/ShareType/actions/Create.jsx b/src/pages/share/containers/ShareType/actions/Create.jsx new file mode 100644 index 00000000..26736314 --- /dev/null +++ b/src/pages/share/containers/ShareType/actions/Create.jsx @@ -0,0 +1,162 @@ +// 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 { ModalAction } from 'containers/Action'; +import globalShareTypeStore from '@/stores/manila/share-type'; +import { projectTableOptions } from 'resources/project'; +import { ProjectStore } from 'stores/keystone/project'; +import KeyValueInput from 'components/FormItem/KeyValueInput'; +import { isEmpty } from 'lodash'; +import { yesNoOptions } from 'resources/share-type'; +import { updateAddSelectValueToObj } from 'utils/index'; + +const checkKeyValue = (values) => { + if (isEmpty(values)) { + return true; + } + const item = values.find((it) => { + const { key, value } = it.value || {}; + return !key || value === undefined || value === null; + }); + return !item; +}; + +export const extraFormItem = { + name: 'extra', + label: t('Extra Specs'), + type: 'add-select', + itemComponent: KeyValueInput, + addText: t('Add Extra Spec'), + keySpan: 8, + validator: (rule, value) => { + if (!checkKeyValue(value)) { + // eslint-disable-next-line prefer-promise-reject-errors + return Promise.reject(t('Please enter complete key value!')); + } + return Promise.resolve(); + }, +}; + +export class Create extends ModalAction { + static id = 'create'; + + static title = t('Create Share Type'); + + get name() { + return t('create share type'); + } + + init() { + this.store = globalShareTypeStore; + this.projectStore = new ProjectStore(); + this.getProjects(); + } + + getProjects() { + this.projectStore.fetchList(); + } + + get projects() { + return this.projectStore.list.data || []; + } + + static policy = 'manila:share_type:create'; + + static allowed = () => Promise.resolve(true); + + static get modalSize() { + return 'large'; + } + + getModalSize() { + return 'large'; + } + + get nameForStateUpdate() { + return ['isPublic']; + } + + get defaultValue() { + return { isPublic: true }; + } + + get formItems() { + const { isPublic } = this.state; + return [ + { + name: 'name', + label: t('Name'), + type: 'input-name', + names: this.store.list.data.map((it) => it.name), + required: true, + }, + { + name: 'description', + label: t('Description'), + type: 'textarea', + }, + { + name: 'driver_handles_share_servers', + label: t('Driver Handles Share Servers'), + type: 'select', + options: yesNoOptions, + required: true, + }, + { + name: 'isPublic', + label: t('Public'), + type: 'check', + content: t('Public'), + required: true, + }, + { + name: 'accessControl', + label: t('Access Control'), + type: 'select-table', + isMulti: true, + hidden: isPublic, + data: this.projects, + isLoading: this.projectStore.list.isLoading, + ...projectTableOptions, + }, + extraFormItem, + ]; + } + + onSubmit = (values) => { + const { + driver_handles_share_servers, + isPublic = false, + accessControl = {}, + extra = [], + ...rest + } = values; + const body = { ...rest }; + let projectIds = []; + const extraSpecs = updateAddSelectValueToObj(extra); + extraSpecs.driver_handles_share_servers = driver_handles_share_servers; + body.extra_specs = extraSpecs; + if (isPublic) { + body['os-share-type-access:is_public'] = true; + } else { + body['os-share-type-access:is_public'] = false; + const { selectedRowKeys = [] } = accessControl; + projectIds = [...selectedRowKeys]; + } + return this.store.create(body, projectIds); + }; +} + +export default inject('rootStore')(observer(Create)); diff --git a/src/pages/share/containers/ShareType/actions/Delete.jsx b/src/pages/share/containers/ShareType/actions/Delete.jsx new file mode 100644 index 00000000..677e20a8 --- /dev/null +++ b/src/pages/share/containers/ShareType/actions/Delete.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 { ConfirmAction } from 'containers/Action'; +import globalShareTypeStore from '@/stores/manila/share-type'; + +export default class Delete extends ConfirmAction { + get id() { + return 'delete'; + } + + get title() { + return t('Delete Share Type'); + } + + get buttonType() { + return 'danger'; + } + + get buttonText() { + return t('Delete'); + } + + get actionName() { + return t('Delete Share Type'); + } + + policy = 'manila:share_type:delete'; + + onSubmit = (data) => globalShareTypeStore.delete(data); +} diff --git a/src/pages/share/containers/ShareType/actions/Edit.jsx b/src/pages/share/containers/ShareType/actions/Edit.jsx new file mode 100644 index 00000000..fac23b89 --- /dev/null +++ b/src/pages/share/containers/ShareType/actions/Edit.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 { inject, observer } from 'mobx-react'; +import { ModalAction } from 'containers/Action'; +import globalShareTypeStore from '@/stores/manila/share-type'; + +export class Edit extends ModalAction { + static id = 'edit'; + + static title = t('Edit'); + + get defaultValue() { + const { name, description } = this.item; + const value = { + name, + description, + }; + return value; + } + + static policy = 'manila:share_type:update'; + + static allowed = () => Promise.resolve(true); + + get formItems() { + return [ + { + name: 'name', + label: t('Name'), + type: 'input-name', + names: this.store.list.data + .filter((it) => it.id !== this.item.id) + .map((it) => it.name), + required: true, + }, + { + name: 'description', + label: t('Description'), + type: 'textarea', + }, + ]; + } + + init() { + this.store = globalShareTypeStore; + } + + onSubmit = (values) => { + const { id } = this.item; + return this.store.update(id, values); + }; +} + +export default inject('rootStore')(observer(Edit)); diff --git a/src/pages/share/containers/ShareType/actions/ManageAccess.jsx b/src/pages/share/containers/ShareType/actions/ManageAccess.jsx new file mode 100644 index 00000000..2f09f697 --- /dev/null +++ b/src/pages/share/containers/ShareType/actions/ManageAccess.jsx @@ -0,0 +1,140 @@ +// 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 globalShareTypeStore, { + ShareTypeStore, +} from '@/stores/manila/share-type'; +import { ModalAction } from 'containers/Action'; +import { ProjectStore } from 'stores/keystone/project'; +import { projectTableOptions } from 'resources/project'; + +export class ManageAccess extends ModalAction { + static id = 'manage-access'; + + static title = t('Manage Access'); + + init() { + this.store = new ShareTypeStore(); + this.projectStore = new ProjectStore(); + this.getAccess(); + this.getProjects(); + } + + static get modalSize() { + return 'large'; + } + + getModalSize() { + return 'large'; + } + + static policy = [ + 'manila:share_type:list_project_access', + 'manila:share_type:add_project_access', + 'manila:share_type:remove_project_access', + ]; + + static allowed = () => Promise.resolve(true); + + async getAccess() { + const { is_public } = this.item; + if (!is_public) { + await this.store.fetchProjectAccess(this.item.id); + this.updateDefaultValue(); + } + } + + async getProjects() { + await this.projectStore.fetchList(); + this.updateDefaultValue(); + } + + get name() { + return t('Manage Access'); + } + + get projects() { + return this.projectStore.list.data || []; + } + + get defaultValue() { + const { name, is_public: isPublic } = this.item; + return { + name, + isPublic, + access: { + selectedRowKeys: this.currentAccess, + }, + }; + } + + get currentAccess() { + return (this.store.access || []).map((it) => it.project_id); + } + + get nameForStateUpdate() { + return ['isPublic']; + } + + get formItems() { + const { isPublic } = this.state; + return [ + { + name: 'name', + label: t('Share Type'), + type: 'label', + iconType: 'volume', + }, + { + // 'os-volume-type-access:is_public' + name: 'isPublic', + label: t('Public'), + type: 'check', + content: t('Public'), + }, + { + name: 'access', + label: t('Access Control'), + type: 'select-table', + isMulti: true, + hidden: isPublic, + data: this.projects, + isLoading: this.projectStore.list.isLoading, + ...projectTableOptions, + }, + ]; + } + + onSubmit = (values) => { + const { access = {}, isPublic } = values; + const { is_public: publicOld, id } = this.item; + const body = { id }; + if (isPublic !== publicOld) { + body.newPublic = isPublic; + } + if (!isPublic) { + const { selectedRowKeys = [] } = access; + body.adds = selectedRowKeys.filter( + (it) => this.currentAccess.indexOf(it) < 0 + ); + body.dels = this.currentAccess.filter( + (it) => selectedRowKeys.indexOf(it) < 0 + ); + } + return globalShareTypeStore.updateProjectAccess(body); + }; +} + +export default inject('rootStore')(observer(ManageAccess)); diff --git a/src/pages/share/containers/ShareType/actions/index.jsx b/src/pages/share/containers/ShareType/actions/index.jsx new file mode 100644 index 00000000..e3583a75 --- /dev/null +++ b/src/pages/share/containers/ShareType/actions/index.jsx @@ -0,0 +1,36 @@ +// 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 Create from './Create'; +import Edit from './Edit'; +import Delete from './Delete'; +import ManageAccess from './ManageAccess'; + +const actionConfigs = { + rowActions: { + firstAction: Edit, + moreActions: [ + { + action: ManageAccess, + }, + { + action: Delete, + }, + ], + }, + primaryActions: [Create], + batchActions: [Delete], +}; + +export default actionConfigs; diff --git a/src/pages/share/containers/ShareType/index.jsx b/src/pages/share/containers/ShareType/index.jsx new file mode 100644 index 00000000..49b88504 --- /dev/null +++ b/src/pages/share/containers/ShareType/index.jsx @@ -0,0 +1,68 @@ +// 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 { observer, inject } from 'mobx-react'; +import Base from 'containers/List'; +import globalShareTypeStore from '@/stores/manila/share-type'; +import actionConfigs from './actions'; + +export class ShareType extends Base { + init() { + this.store = globalShareTypeStore; + } + + get policy() { + return 'manila:share_type:index'; + } + + get name() { + return t('share types'); + } + + get fetchDataByAllProjects() { + return false; + } + + get actionConfigs() { + return actionConfigs; + } + + updateFetchParams = (params) => { + return { + ...params, + is_public: 'all', + }; + }; + + getColumns = () => [ + { + title: t('ID/Name'), + dataIndex: 'name', + routeName: 'shareTypeDetailAdmin', + }, + { + title: t('Description'), + dataIndex: 'description', + isHideable: true, + valueRender: 'noValue', + }, + { + title: t('Public'), + dataIndex: 'share_type_access:is_public', + valueRender: 'yesNo', + }, + ]; +} + +export default inject('rootStore')(observer(ShareType)); diff --git a/src/pages/share/routes/index.js b/src/pages/share/routes/index.js new file mode 100644 index 00000000..d1e8773f --- /dev/null +++ b/src/pages/share/routes/index.js @@ -0,0 +1,35 @@ +// 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 ShareType from '../containers/ShareType'; +import ShareTypeDetail from '../containers/ShareType/Detail'; + +const PATH = '/share'; +export default [ + { + path: PATH, + component: BaseLayout, + routes: [ + { path: `${PATH}/share-type-admin`, component: ShareType, exact: true }, + { + path: `${PATH}/share-type-admin/detail/:id`, + component: ShareTypeDetail, + exact: true, + }, + { path: '*', component: E404 }, + ], + }, +]; diff --git a/src/resources/share-type.js b/src/resources/share-type.js new file mode 100644 index 00000000..dbf35d18 --- /dev/null +++ b/src/resources/share-type.js @@ -0,0 +1,4 @@ +export const yesNoOptions = [ + { label: t('Yes'), key: true, value: true }, + { label: t('No'), key: false, value: false }, +]; diff --git a/src/stores/manila/extra-spec.js b/src/stores/manila/extra-spec.js new file mode 100644 index 00000000..522aca9d --- /dev/null +++ b/src/stores/manila/extra-spec.js @@ -0,0 +1,58 @@ +// 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 { action } from 'mobx'; +import client from 'client'; +import Base from 'stores/base'; + +export class ExtraSpecStore extends Base { + get client() { + return client.manila.types.extraSpecs; + } + + get isSubResource() { + return true; + } + + getFatherResourceId = (params) => params.id; + + getListDataFromResult = (result) => { + const { extra_specs } = result; + const data = []; + Object.keys(extra_specs).forEach((key) => { + data.push({ + id: key, + keyName: key, + name: key, + value: extra_specs[key], + }); + }); + return data; + }; + + @action + createOrUpdate(id, data) { + const body = { + extra_specs: data, + }; + return this.submitting(this.client.create(id, body)); + } + + @action + delete = ({ id, keyName }) => { + return this.submitting(this.client.delete(id, keyName)); + }; +} +const globalExtraSpecStore = new ExtraSpecStore(); +export default globalExtraSpecStore; diff --git a/src/stores/manila/share-type.js b/src/stores/manila/share-type.js new file mode 100644 index 00000000..44606471 --- /dev/null +++ b/src/stores/manila/share-type.js @@ -0,0 +1,113 @@ +// 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 { action, observable } from 'mobx'; +import client from 'client'; +import Base from 'stores/base'; + +export class ShareTypeStore extends Base { + @observable + access = []; + + get client() { + return client.manila.types; + } + + get paramsFunc() { + return (params) => params; + } + + get mapper() { + return (data) => { + return { + ...data, + is_public: data['share_type_access:is_public'], + }; + }; + } + + @action + async create(data, projectIds = []) { + const body = {}; + body[this.responseKey] = data; + if (projectIds.length === 0) { + return this.submitting(this.client.create(body)); + } + this.isSubmitting = true; + const result = await this.client.create(body); + const { id } = result[this.responseKey]; + return this.addProjectAccess(id, projectIds); + } + + @action + update(id, data) { + const body = {}; + body[this.responseKey] = data; + return this.submitting(this.client.update(id, body)); + } + + @action + addProjectAccess(id, projectIds = []) { + return this.submitting( + Promise.all( + projectIds.map((it) => { + const actionBody = { + addProjectAccess: { + project: it, + }, + }; + return this.client.action(id, actionBody); + }) + ) + ); + } + + @action + removeProjectAccess(id, projectIds = []) { + return this.submitting( + Promise.all( + projectIds.map((it) => { + const actionBody = { + removeProjectAccess: { + project: it, + }, + }; + return this.client.action(id, actionBody); + }) + ) + ); + } + + @action + async updateProjectAccess({ id, adds = [], dels = [], newPublic }) { + const more = adds.length > 0 || dels.length > 0; + if (newPublic !== undefined) { + if (newPublic || !more) { + return this.update(id, { 'share_type_access:is_public': newPublic }); + } + await this.update(id, { 'share_type_access:is_public': newPublic }); + } + await this.removeProjectAccess(id, dels); + return this.addProjectAccess(id, adds); + } + + @action + async fetchProjectAccess(id) { + const result = await this.client.getAccess(id); + this.access = result.share_type_access; + } +} + +const globalShareTypeStore = new ShareTypeStore(); +export default globalShareTypeStore;