feat: Support domain actions

1. Support create/edit/delete/enable/disable domain actions
2. Set enableColumn to deal with enable column for domain/user/project
3. Update domain detail e2e
4. Update domain store for better fetch user/project data

Change-Id: I37bf708bca9c819f0d5a63d59dafef1959ec8503
This commit is contained in:
Jingwei.Zhang 2022-06-06 17:44:31 +08:00
parent 083e44a401
commit 5a8ec658d5
24 changed files with 146 additions and 324 deletions

View File

@ -528,19 +528,6 @@ const renderMenu = (t) => {
key: 'domainAdmin',
level: 1,
children: [
{
path: '/identity/domain-admin/create',
name: t('Create Domain'),
key: 'domainCreateAdmin',
level: 2,
},
{
path: /^\/identity\/domain-admin\/edit\/.[^/]+$/,
name: t('Domain Edit'),
key: 'domainEditAdmin',
level: 2,
routePath: '/identity/domain-admin/edit/:id',
},
{
path: /^\/identity\/domain-admin\/detail\/.[^/]+$/,
name: t('Domain Detail'),

View File

@ -732,7 +732,6 @@
"Docker Volume Size (GiB)": "Docker Volume Size (GiB)",
"Domain": "Domain",
"Domain Detail": "Domain Detail",
"Domain Edit": "Domain Edit",
"Domain ID/Name": "Domain ID/Name",
"Domain Manager": "Domain Manager",
"Domain Name": "Domain Name",
@ -2401,7 +2400,6 @@
"User Groups": "User Groups",
"User ID": "User ID",
"User ID/Name": "User ID/Name",
"User List": "User List",
"User Name": "User Name",
"User Num": "User Num",
"User Num: ": "User Num: ",
@ -2622,7 +2620,6 @@
"edit": "edit",
"edit baremetal node": "edit baremetal node",
"edit default pool": "edit default pool",
"edit domain": "edit domain",
"edit health monitor": "edit health monitor",
"edit image": "edit image",
"edit member": "edit member",

View File

@ -732,7 +732,6 @@
"Docker Volume Size (GiB)": "Docker硬盘大小(GiB)",
"Domain": "域",
"Domain Detail": "域详情",
"Domain Edit": "编辑域",
"Domain ID/Name": "域ID/名称",
"Domain Manager": "域管理员",
"Domain Name": "域名",
@ -2401,7 +2400,6 @@
"User Groups": "用户组",
"User ID": "用户ID",
"User ID/Name": "用户ID/名称",
"User List": "用户列表",
"User Name": "用户名称",
"User Num": "用户数",
"User Num: ": "用户数: ",
@ -2622,7 +2620,6 @@
"edit": "编辑",
"edit baremetal node": "编辑裸机节点",
"edit default pool": "编辑资源池",
"edit domain": "编辑域",
"edit health monitor": "编辑健康检查器",
"edit image": "编辑镜像",
"edit member": "编辑成员",

View File

@ -40,10 +40,6 @@ export class Instance extends Base {
this.downloadStore = new ServerStore();
}
get tabs() {
return [];
}
get policy() {
if (this.isAdminPage) {
return 'os_compute_api:servers:index:get_all_tenants';

View File

@ -12,12 +12,13 @@
// 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 { Badge } from 'antd';
import { DomainStore } from 'stores/keystone/domain';
import Base from 'containers/TabDetail';
import { enabledColumn } from 'resources/keystone/domain';
import User from '../../User';
import Project from '../../Project';
import actionConfigs from '../actions';
export class DomainDetail extends Base {
get name() {
@ -32,26 +33,24 @@ export class DomainDetail extends Base {
return this.getRoutePath('domain');
}
get actionConfigs() {
return actionConfigs;
}
get detailInfos() {
return [
{
title: t('Domain Name'),
dataIndex: 'name',
},
{
title: t('Enabled'),
dataIndex: 'enabled',
isHideable: true,
render: (val) => {
if (val === true) {
return <Badge color="green" text={t('Yes')} />;
}
return <Badge color="red" text={t('No')} />;
},
},
enabledColumn,
{
title: t('User Num'),
dataIndex: 'user_num',
dataIndex: 'userCount',
},
{
title: t('Project Num'),
dataIndex: 'projectCount',
},
{
title: t('Description'),
@ -63,21 +62,19 @@ export class DomainDetail extends Base {
get tabs() {
const tabs = [
{
title: t('User List'),
title: t('Users'),
key: 'user',
component: User,
},
{
title: t('Projects'),
key: 'project',
component: Project,
},
];
return tabs;
}
goEdit = () => {
const {
params: { id },
} = this.props.match;
this.routing.push(`${this.listUrl}/edit/${id}`);
};
init() {
this.store = new DomainStore();
}

View File

@ -14,10 +14,10 @@
import { inject, observer } from 'mobx-react';
import globalDomainStore from 'stores/keystone/domain';
import { FormAction } from 'containers/Action';
import { statusTypes } from 'utils/constants';
import { ModalAction } from 'containers/Action';
import { statusTypes } from 'resources/keystone/domain';
export class CreateForm extends FormAction {
export class Create extends ModalAction {
init() {
this.store = globalDomainStore;
}
@ -26,29 +26,16 @@ export class CreateForm extends FormAction {
static title = t('Create Domain');
static path = '/identity/domain-admin/create';
static policy = 'identity:create_domain';
static allowed() {
return Promise.resolve(true);
}
get listUrl() {
return this.getRoutePath('domain');
}
get name() {
return t('Create Domain');
}
get labelCol() {
return {
xs: { span: 6 },
sm: { span: 5 },
};
}
get defaultValue() {
const data = {
enabled: statusTypes[0],
@ -100,4 +87,4 @@ export class CreateForm extends FormAction {
};
}
export default inject('rootStore')(observer(CreateForm));
export default inject('rootStore')(observer(Create));

View File

@ -38,6 +38,8 @@ export default class DeleteAction extends ConfirmAction {
policy = 'identity:delete_domain';
allowedCheckFunc = (data) => !data.enabled;
onSubmit = (data) => {
const { id } = data;
return globalDomainStore.delete({ id });

View File

@ -13,29 +13,19 @@
// limitations under the License.
import { inject, observer } from 'mobx-react';
import { FormAction } from 'containers/Action';
import { ModalAction } from 'containers/Action';
import globalDomainStore from 'stores/keystone/domain';
export class EditForm extends FormAction {
export class Edit extends ModalAction {
init() {
this.store = globalDomainStore;
}
componentDidMount() {
const { item } = this.props;
const {
params: { id },
} = this.props.match;
this.store.fetchDetail({ ...item, id });
}
static id = 'domain-edit';
static title = t('Edit Domain');
static path(item) {
return `/identity/domain-admin/edit/${item.id}`;
}
static buttonText = t('Edit');
static policy = 'identity:update_domain';
@ -43,45 +33,12 @@ export class EditForm extends FormAction {
return Promise.resolve(true);
}
get listUrl() {
return this.getRoutePath('domain');
}
get data() {
return this.store.detail || [];
}
get name() {
const { name } = this.data;
return `${t('edit domain')} ${name}`;
}
get labelCol() {
return {
xs: { span: 6 },
sm: { span: 5 },
};
}
get domainUserList() {
return (this.store.domainUsers || []).map((it) => ({
label: it.name,
value: it.id,
}));
}
get defaultValue() {
const { name, description, domain_administrator } = this.store.detail;
const userIds = [];
(domain_administrator || []).map((it) => userIds.push(it.id));
if (name && this.formRef.current) {
this.formRef.current.setFieldsValue({
name,
description,
adminUsers: userIds,
});
}
return {};
const { name, description } = this.item;
return {
name,
description,
};
}
get formItems() {
@ -95,13 +52,6 @@ export class EditForm extends FormAction {
help: t('The name cannot be modified after creation'),
disabled: true,
},
{
name: 'adminUsers',
label: t('Domain Manager'),
type: 'select',
mode: 'multiple',
options: this.domainUserList,
},
{
name: 'description',
label: t('Description'),
@ -111,32 +61,9 @@ export class EditForm extends FormAction {
}
onSubmit = async (values) => {
const { description, adminUsers } = values;
const {
adminRoleId: role_id,
detail: { domain_administrator: oldUsers },
} = this.store;
const {
params: { id },
} = this.props.match;
const promiseList = [];
oldUsers.forEach((user) => {
const { id: user_id } = user;
if (adminUsers.indexOf(user_id) === -1) {
promiseList.push(
globalDomainStore.deleteDomainAdmin({ id, user_id, role_id })
);
}
});
adminUsers.forEach((user_id) =>
promiseList.push(
globalDomainStore.setDomainAdmin({ id, user_id, role_id })
)
);
promiseList.push(globalDomainStore.edit({ id, description }));
const results = await Promise.all(promiseList);
return results;
const { id } = this.item;
return this.store.edit({ id, ...values });
};
}
export default inject('rootStore')(observer(EditForm));
export default inject('rootStore')(observer(Edit));

View File

@ -33,6 +33,10 @@ export default class ForbiddenAction extends ConfirmAction {
return t('Forbidden Domain');
}
get isDanger() {
return true;
}
policy = 'identity:update_domain';
allowedCheckFunc = (item) => {

View File

@ -12,29 +12,29 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import createActionConfig from './Create';
import deleteActionConfig from './Delete';
import editActionConfig from './Edit';
import enableActionConfig from './Enable';
import forbiddenActionConfig from './Forbidden';
import Create from './Create';
import Delete from './Delete';
import Edit from './Edit';
import Enable from './Enable';
import Forbidden from './Forbidden';
const actionConfigs = {
rowActions: {
firstAction: editActionConfig,
firstAction: Edit,
moreActions: [
{
action: deleteActionConfig,
action: Delete,
},
{
action: enableActionConfig,
action: Enable,
},
{
action: forbiddenActionConfig,
action: Forbidden,
},
],
},
batchActions: [deleteActionConfig],
primaryActions: [createActionConfig],
batchActions: [Delete],
primaryActions: [Create],
};
export default actionConfigs;

View File

@ -15,16 +15,14 @@
import { observer, inject } from 'mobx-react';
import Base from 'containers/List';
import globalDomainStore from 'stores/keystone/domain';
import { enabledColumn } from 'resources/keystone/domain';
import actionConfigs from './actions';
export class Domains extends Base {
init() {
this.store = globalDomainStore;
}
get tabs() {
return [];
}
get policy() {
return 'identity:list_domains';
}
@ -37,6 +35,10 @@ export class Domains extends Base {
return false;
}
get actionConfigs() {
return actionConfigs;
}
getColumns = () => [
{
title: t('Domain ID/Name'),
@ -44,11 +46,16 @@ export class Domains extends Base {
routeName: 'domainDetailAdmin',
},
{
title: t('Member Num'),
dataIndex: 'user_num',
title: t('User Num'),
dataIndex: 'userCount',
isHideable: true,
render: (user_num) => `${t('User Num: ')}${user_num}`,
},
{
title: t('Project Num'),
dataIndex: 'projectCount',
isHideable: true,
},
enabledColumn,
{
title: t('Description'),
dataIndex: 'description',

View File

@ -12,11 +12,10 @@
// 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 { Badge } from 'antd';
import { ProjectStore } from 'stores/keystone/project';
import Base from 'containers/TabDetail';
import { enabledColumn } from 'resources/keystone/domain';
import UserGroup from '../../UserGroup';
import User from '../../User';
import Quota from './Quota';
@ -53,17 +52,7 @@ export class Detail extends Base {
title: t('Project Name'),
dataIndex: 'name',
},
{
title: t('Enabled'),
dataIndex: 'enabled',
isHideable: true,
render: (val) => {
if (val === true) {
return <Badge color="green" text={t('Yes')} />;
}
return <Badge color="red" text={t('No')} />;
},
},
enabledColumn,
{
title: t('User Num'),
dataIndex: 'user_num',

View File

@ -17,7 +17,7 @@ import { ModalAction } from 'containers/Action';
import globalDomainStore from 'stores/keystone/domain';
import globalProjectStore from 'stores/keystone/project';
import { regex } from 'utils/validate';
import { statusTypes } from 'utils/constants';
import { statusTypes } from 'resources/keystone/domain';
export class CreateForm extends ModalAction {
constructor(props) {

View File

@ -15,7 +15,7 @@
import { inject, observer } from 'mobx-react';
import { ModalAction } from 'containers/Action';
import globalProjectStore from 'stores/keystone/project';
import { statusTypes } from 'utils/constants';
import { statusTypes } from 'resources/keystone/domain';
export class EditForm extends ModalAction {
init() {

View File

@ -14,11 +14,12 @@
import React from 'react';
import { observer, inject } from 'mobx-react';
import { Divider, Badge } from 'antd';
import { Divider } from 'antd';
import Base from 'containers/List';
import globalProjectStore, { ProjectStore } from 'stores/keystone/project';
import { yesNoOptions, emptyActionConfig } from 'utils/constants';
import { SimpleTag } from 'resources/nova/instance';
import { enabledColumn } from 'resources/keystone/domain';
import actionConfigs from './actions';
import styles from './index.less';
@ -106,18 +107,7 @@ export class Projects extends Base {
)}${group_num}`;
},
},
{
title: t('Enabled'),
dataIndex: 'enabled',
isHideable: true,
render: (val) => {
if (val === true) {
return <Badge color="green" text={t('Yes')} />;
}
return <Badge color="red" text={t('No')} />;
},
stringify: (enabled) => (enabled ? t('Yes') : t('No')),
},
enabledColumn,
{
title: t('Tags'),
dataIndex: 'tags',

View File

@ -12,13 +12,12 @@
// 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 { Badge } from 'antd';
import { UserStore } from 'stores/keystone/user';
import globalDomainStore from 'stores/keystone/domain';
import Base from 'containers/TabDetail';
import Credentials from 'src/pages/user-center/containers/Credentials';
import { enabledColumn } from 'resources/keystone/domain';
import UserGroup from '../../UserGroup';
import Project from '../../Project';
import actionConfigs from '../actions';
@ -70,17 +69,7 @@ export class UserDetail extends Base {
title: t('User Name'),
dataIndex: 'name',
},
{
title: t('Enabled'),
dataIndex: 'enabled',
isHideable: true,
render: (val) => {
if (val === true) {
return <Badge color="green" text={t('Yes')} />;
}
return <Badge color="red" text={t('No')} />;
},
},
enabledColumn,
{
title: t('Real Name'),
dataIndex: 'real_name',

View File

@ -26,7 +26,7 @@ import {
phoneNumberValidate,
emailValidate,
} from 'utils/validate';
import { statusTypes } from 'utils/constants';
import { statusTypes } from 'resources/keystone/domain';
export class CreateForm extends FormAction {
constructor(props) {

View File

@ -20,7 +20,7 @@ import globalProjectStore from 'stores/keystone/project';
import globalRoleStore from 'stores/keystone/role';
import { getPasswordOtherRule, phoneNumberValidate } from 'utils/validate';
import globalDomainStore from 'stores/keystone/domain';
import { statusTypes } from 'utils/constants';
import { statusTypes } from 'resources/keystone/domain';
export class CreateForm extends ModalAction {
init() {

View File

@ -20,6 +20,7 @@ import globalUserStore, { UserStore } from 'stores/keystone/user';
import { yesNoOptions, emptyActionConfig } from 'utils/constants';
import { Link } from 'react-router-dom';
import { FileTextOutlined } from '@ant-design/icons';
import { enabledColumn } from 'resources/keystone/domain';
import actionConfigs from './actions';
import actionConfigsInDomain from './actionsInDomain';
@ -204,18 +205,7 @@ export class User extends Base {
dataIndex: 'phone',
isHideable: true,
},
{
title: t('Enabled'),
dataIndex: 'enabled',
isHideable: true,
render: (val) => {
if (val === true) {
return <Badge color="green" text={t('Yes')} />;
}
return <Badge color="red" text={t('No')} />;
},
stringify: (val) => (val ? t('Yes') : t('No')),
},
enabledColumn,
];
if (!this.inDetailPage) {
return columns.filter(

View File

@ -15,8 +15,6 @@
import BaseLayout from 'layouts/Basic';
import E404 from 'pages/base/containers/404';
import Domain from '../containers/Domain';
import DomainCreate from '../containers/Domain/actions/Create';
import DomainEdit from '../containers/Domain/actions/Edit';
import DomainDetail from '../containers/Domain/Detail';
import Project from '../containers/Project';
import ProjectCreate from '../containers/Project/actions/Create';
@ -45,16 +43,6 @@ export default [
component: DomainDetail,
exact: true,
},
{
path: `${PATH}/domain-admin/create`,
component: DomainCreate,
exact: true,
},
{
path: `${PATH}/domain-admin/edit/:id`,
component: DomainEdit,
exact: true,
},
{ path: `${PATH}/project`, component: Project, exact: true },
{ path: `${PATH}/project-admin`, component: Project, exact: true },
{ path: `${PATH}/project/create`, component: ProjectCreate, exact: true },

View File

@ -0,0 +1,26 @@
import React from 'react';
import { Badge } from 'antd';
export const statusTypes = [
{
label: t('Enable'),
value: true,
},
{
label: t('Forbidden'),
value: false,
},
];
export const enabledColumn = {
title: t('Enabled'),
dataIndex: 'enabled',
isHideable: true,
render: (val) => {
if (val === true) {
return <Badge color="green" text={t('Yes')} />;
}
return <Badge color="red" text={t('No')} />;
},
stringify: (val) => (val ? t('Yes') : t('No')),
};

View File

@ -13,7 +13,6 @@
// limitations under the License.
import { action, observable } from 'mobx';
import { get } from 'lodash';
import client from 'client';
import Base from 'stores/base';
@ -21,12 +20,6 @@ export class DomainStore extends Base {
@observable
domains = [];
@observable
domainUsers = [];
@observable
adminRoleId = '';
get client() {
return client.keystone.domains;
}
@ -35,66 +28,50 @@ export class DomainStore extends Base {
return client.keystone.users;
}
@action
async fetchList({
limit,
page,
sortKey,
sortOrder,
conditions,
...filters
} = {}) {
this.list.isLoading = true;
// todo: no page, no limit, fetch all
// const params = { ...filters };
await Promise.all([this.client.list(), this.userClient.list()]).then(
([domainsResult, usersResult]) => {
const { domains } = domainsResult;
// eslint-disable-next-line array-callback-return
domains.map((domain) => {
const domainUsers = usersResult.users.filter(
(it) => it.domain_id === domain.id
);
domain.user_num = domainUsers.length;
});
// const { domains: items } = domainsResult;
this.list.update({
data: domains,
total: domains.length || 0,
limit: Number(limit) || 10,
page: Number(page) || 1,
sortKey,
sortOrder,
filters,
isLoading: false,
...(this.list.silent ? {} : { selectedRowKeys: [] }),
});
return domains;
}
);
get projectClient() {
return client.keystone.projects;
}
@action
async fetchDetail({ id, silent }) {
if (!silent) {
this.isLoading = true;
async listDidFetch(items) {
if (!items.length) {
return items;
}
await Promise.all([this.client.show(id), this.userClient.list()]).then(
([result, usersResult]) => {
const domain = this.mapper(get(result, this.responseKey) || result);
domain.domain_administrator = [];
const domainUsers = usersResult.users.filter(
(it) => it.domain_id === domain.id
);
domain.user_num = domainUsers.length;
this.domainUsers = domainUsers;
this.detail = domain;
this.isLoading = false;
return domain;
}
);
const [userResult, projectResult] = await Promise.all([
this.userClient.list(),
this.projectClient.list(),
]);
return items.map((it) => {
const users = (userResult.users || []).filter(
(user) => user.domain_id === it.id
);
const projects = (projectResult.projects || []).filter(
(project) => project.domain_id === it.id
);
return {
...it,
users,
userCount: users.length,
projects,
projectCount: projects.length,
};
});
}
async detailDidFetch(item) {
const { id } = item;
const [userResult, projectResult] = await Promise.all([
this.userClient.list({ domain_id: id }),
this.projectClient.list({ domain_id: id }),
]);
const { users = [] } = userResult || {};
const { projects = [] } = projectResult || {};
return {
...item,
users,
userCount: users.length,
projects,
projectCount: projects.length,
};
}
@action
@ -103,14 +80,6 @@ export class DomainStore extends Base {
this.domains = domainsResult.domains;
}
@action
async update({ id, body }) {
this.isSubmitting = true;
const resData = await this.client.update(id, body);
this.isSubmitting = false;
return resData;
}
@action
async edit({ id, description }) {
const reqBody = {
@ -119,15 +88,6 @@ export class DomainStore extends Base {
return this.submitting(this.client.patch(id, reqBody));
}
async setDomainAdmin({ id, user_id, role_id }) {
return this.submitting(this.client.users.roles.put(id, user_id, role_id));
}
async deleteDomainAdmin({ id, user_id, role_id }) {
const result = await this.client.users.roles.delete(id, user_id, role_id);
return result;
}
@action
async forbidden({ id }) {
const reqBody = {

View File

@ -140,14 +140,3 @@ export const projectTagsColors = shuffle([
'geekblue',
'purple',
]);
export const statusTypes = [
{
label: t('Enable'),
value: true,
},
{
label: t('Forbidden'),
value: false,
},
];

View File

@ -23,7 +23,7 @@ describe('The Domain Page', () => {
});
it('successfully detail', () => {
cy.tableSearchText(name).goToDetail(0);
cy.tableSearchText(name).goToDetail();
cy.goBackToList(listUrl);
});
});