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;