feat(manager/users/share): Enhance device sharing functionality and update related APIs and UI components

This commit is contained in:
Tran Anh Tuan
2026-01-23 11:29:35 +07:00
parent 8b95a620c2
commit 6691122c8f
11 changed files with 782 additions and 380 deletions

View File

@@ -13,17 +13,23 @@ import TreeGroup from './TreeGroup';
const { Paragraph } = Typography; const { Paragraph } = Typography;
type ThingsFilterProps = { type ThingsFilterProps = {
title?: string;
isOpen?: boolean; isOpen?: boolean;
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>; setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
thingIds?: string | string[] | null; thingIds?: string | string[] | null;
extra?: React.ReactNode;
onSubmit?: (thingIds: string[]) => void; onSubmit?: (thingIds: string[]) => void;
disabled?: boolean;
}; };
const ThingsFilter = ({ const ThingsFilter = ({
title,
isOpen, isOpen,
setIsOpen, setIsOpen,
thingIds, thingIds,
extra,
onSubmit, onSubmit,
disabled = false,
}: ThingsFilterProps) => { }: ThingsFilterProps) => {
const intl = useIntl(); const intl = useIntl();
const { useBreakpoint } = Grid; const { useBreakpoint } = Grid;
@@ -93,6 +99,7 @@ const ThingsFilter = ({
]; ];
return ( return (
<Modal <Modal
title={title ? title : null}
open={isOpen} open={isOpen}
centered centered
width="80%" width="80%"
@@ -104,7 +111,10 @@ const ThingsFilter = ({
<FormattedMessage id="common.cancel" defaultMessage="Cancel" /> <FormattedMessage id="common.cancel" defaultMessage="Cancel" />
} }
> >
<ProCard split={screens.md ? 'vertical' : 'horizontal'}> <ProCard
split={screens.md ? 'vertical' : 'horizontal'}
extra={extra ? extra : null}
>
<ProCard colSpan={{ xs: 24, sm: 8, md: 8, lg: 6, xl: 6 }}> <ProCard colSpan={{ xs: 24, sm: 8, md: 8, lg: 6, xl: 6 }}>
<TreeGroup <TreeGroup
disable={isLoading} disable={isLoading}
@@ -128,6 +138,15 @@ const ThingsFilter = ({
alwaysShowAlert: true, alwaysShowAlert: true,
selectedRowKeys, selectedRowKeys,
onChange: (keys) => setSelectedRowKeys(keys), onChange: (keys) => setSelectedRowKeys(keys),
getCheckboxProps: (record) => ({
disabled:
disabled && thingIds && record.id
? (Array.isArray(thingIds)
? thingIds
: [thingIds]
).includes(record.id)
: false,
}),
}} }}
pagination={{ pagination={{
size: 'small', size: 'small',

View File

@@ -8,7 +8,8 @@ export const API_ALARMS_CONFIRM = '/api/alarms/confirm';
// Thing API Constants // Thing API Constants
export const API_THINGS_SEARCH = '/api/things/search'; export const API_THINGS_SEARCH = '/api/things/search';
export const API_THINGS_POLICY = '/api/things/policy'; export const API_THING_POLICY = '/api/things/policy2';
export const API_SHARE_THING = '/api/things';
// Group API Constants // Group API Constants
export const API_GROUPS = '/api/groups'; export const API_GROUPS = '/api/groups';

View File

@@ -48,7 +48,22 @@ export default {
'master.users.unassign.fail': 'Unassign failed', 'master.users.unassign.fail': 'Unassign failed',
'master.users.assign.success': 'Assign group successful', 'master.users.assign.success': 'Assign group successful',
'master.users.assign.fail': 'Assign group failed', 'master.users.assign.fail': 'Assign group failed',
'master.users.deletion.title': 'Are you sure to delete this selected items?', 'master.users.delete.title': 'Are you sure to delete this selected items?',
'master.users.delete.success': 'User deleted successfully', 'master.users.delete.success': 'User deleted successfully',
'master.users.delete.fail': 'User deletion failed', 'master.users.delete.fail': 'User deletion failed',
'master.users.things.list': 'List of devices',
'master.users.things.relations.text': 'Relations',
'master.users.things.relation.write': 'Control',
'master.users.things.relation.read': 'View',
'master.users.things.relation.delete': 'Config',
'master.users.thing.unshare.confirm':
'Are you sure you want to stop sharing these devices?',
'master.users.thing.unshare.title': 'Stop Sharing',
'master.users.things.unsharing': 'Unsharing devices...',
'master.users.thing.unshare.success': 'Stop sharing successful',
'master.users.thing.unshare.fail': 'Stop sharing failed',
'master.users.thing.share.title': 'Share things',
'master.users.things.sharing': 'Sharing devices...',
'master.users.thing.share.success': 'Device sharing successful',
'master.users.thing.share.fail': 'Device sharing failed',
}; };

View File

@@ -4,7 +4,7 @@ export default {
'Không thể tạo đơn vị con khi gốc đã có thiết bị', 'Không thể tạo đơn vị con khi gốc đã có thiết bị',
'master.groups.add': 'Tạo đơn vị cấp dưới', 'master.groups.add': 'Tạo đơn vị cấp dưới',
'master.groups.delete.confirm': 'Bạn có chắc muốn xóa nhóm này không?', 'master.groups.delete.confirm': 'Bạn có chắc muốn xóa nhóm này không?',
'master.groups.code': 'Mã đ vị', 'master.groups.code': 'Mã đơn vị',
'master.groups.code.exists': 'Mã đã tồn tại', 'master.groups.code.exists': 'Mã đã tồn tại',
'master.groups.short_name': 'Tên viết tắt', 'master.groups.short_name': 'Tên viết tắt',
'master.groups.short_name.exists': 'Tên viết tắt đã tồn tại', 'master.groups.short_name.exists': 'Tên viết tắt đã tồn tại',

View File

@@ -47,7 +47,22 @@ export default {
'master.users.unassign.fail': 'Ngừng phân quyền thất bại', 'master.users.unassign.fail': 'Ngừng phân quyền thất bại',
'master.users.assign.success': 'Phân quyền đơn vị thành công', 'master.users.assign.success': 'Phân quyền đơn vị thành công',
'master.users.assign.fail': 'Phân quyền đơn vị thất bại', 'master.users.assign.fail': 'Phân quyền đơn vị thất bại',
'master.users.deletion.title': 'Chắc chắn xoá các tài khoản đã chọn?', 'master.users.delete.title': 'Chắc chắn xoá các tài khoản đã chọn?',
'master.users.delete.success': 'Xoá người dùng thành công', 'master.users.delete.success': 'Xoá người dùng thành công',
'master.users.delete.fail': 'Xoá người dùng thất bại', 'master.users.delete.fail': 'Xoá người dùng thất bại',
'master.users.things.list': 'Danh sách thiết bị',
'master.users.things.relations.text': 'Hành động',
'master.users.things.relation.write': 'Điều khiển',
'master.users.things.relation.read': 'Giám sát',
'master.users.things.relation.delete': 'Quản lí',
'master.users.thing.unshare.confirm':
'Chắc chắn muốn ngừng chia sẻ các thiết bị này?',
'master.users.thing.unshare.title': 'Ngừng chia sẻ',
'master.users.thing.unshare.success': 'Ngừng chia sẻ thành công',
'master.users.thing.unshare.fail': 'Ngừng chia sẻ thất bại',
'master.users.things.unsharing': 'Đang ngừng chia sẻ thiết bị...',
'master.users.thing.share.title': 'Chia sẻ thiết bị',
'master.users.things.sharing': 'Đang chia sẻ thiết bị...',
'master.users.thing.share.success': 'Chia sẻ thiết bị thành công',
'master.users.thing.share.fail': 'Chia sẻ thiết bị thất bại',
}; };

View File

@@ -0,0 +1,303 @@
const LogActions = (intl: any) => {
return [
//Alarm
{
title: intl.formatMessage({
id: 'master.logs.things.alarm.confirm',
defaultMessage: 'Alarm confirm',
}),
value: '0-0',
selectable: false,
children: [
{
value: 'things.alarm_confirm',
title: intl.formatMessage({
id: 'master.logs.things.confirm',
defaultMessage: 'Confirm',
}),
},
{
value: 'things.alarm_unconfirm',
title: intl.formatMessage({
id: 'master.logs.things.unconfirm',
defaultMessage: 'Unconfirm',
}),
},
],
},
//Things
{
title: intl.formatMessage({
id: 'master.logs.things',
defaultMessage: 'Things',
}),
value: '0-1',
selectable: false,
children: [
{
value: 'things.create',
title: intl.formatMessage({
id: 'master.logs.things.create',
defaultMessage: 'Create new thing',
}),
},
{
value: 'things.update',
title: intl.formatMessage({
id: 'master.logs.things.update',
defaultMessage: 'Update thing',
}),
},
{
value: 'things.remove',
title: intl.formatMessage({
id: 'master.logs.things.remove',
defaultMessage: 'Remove thing',
}),
},
{
value: 'things.share',
title: intl.formatMessage({
id: 'master.logs.things.share',
defaultMessage: 'Share thing',
}),
},
{
value: 'things.unshare',
title: intl.formatMessage({
id: 'master.logs.things.unshare',
defaultMessage: 'Unshare thing',
}),
},
{
value: 'things.update_key',
title: intl.formatMessage({
id: 'master.logs.things.update_key',
defaultMessage: 'Update key thing',
}),
},
],
},
// Users
{
title: intl.formatMessage({
id: 'master.logs.users',
defaultMessage: 'Users',
}),
value: '0-2',
selectable: false,
children: [
{
value: 'users.create',
title: intl.formatMessage({
id: 'master.logs.users.create',
defaultMessage: 'Register user',
}),
},
{
value: 'users.update',
title: intl.formatMessage({
id: 'master.logs.users.update',
defaultMessage: 'Update user',
}),
},
{
value: 'users.remove',
title: intl.formatMessage({
id: 'master.logs.users.remove',
defaultMessage: 'Remove user',
}),
},
{
value: 'users.login',
title: intl.formatMessage({
id: 'master.logs.users.login',
defaultMessage: 'User login',
}),
},
],
},
// Groups
{
title: intl.formatMessage({
id: 'master.logs.groups',
defaultMessage: 'Groups',
}),
value: '0-3',
selectable: false,
children: [
{
value: 'group.create',
title: intl.formatMessage({
id: 'master.logs.groups.create',
defaultMessage: 'Create new group',
}),
},
{
value: 'group.update',
title: intl.formatMessage({
id: 'master.logs.groups.update',
defaultMessage: 'Update group',
}),
},
{
value: 'group.remove',
title: intl.formatMessage({
id: 'master.logs.groups.remove',
defaultMessage: 'Remove group',
}),
},
{
value: 'group.assign_thing',
title: intl.formatMessage({
id: 'master.logs.groups.assign_thing',
defaultMessage: 'Assign thing to group',
}),
},
{
value: 'group.assign_user',
title: intl.formatMessage({
id: 'master.logs.groups.assign_user',
defaultMessage: 'Assign user to group',
}),
},
{
value: 'group.unassign_thing',
title: intl.formatMessage({
id: 'master.logs.groups.unassign_thing',
defaultMessage: 'Remove thing from group',
}),
},
{
value: 'group.unassign_user',
title: intl.formatMessage({
id: 'master.logs.groups.unassign_user',
defaultMessage: 'Remove user from group',
}),
},
],
},
// Ships
{
title: intl.formatMessage({
id: 'master.logs.ships',
defaultMessage: 'Ships',
}),
value: '0-4',
selectable: false,
children: [
{
value: 'ships.create',
title: intl.formatMessage({
id: 'master.logs.ships.create',
defaultMessage: 'Create new ship',
}),
},
{
value: 'ships.update',
title: intl.formatMessage({
id: 'master.logs.ships.update',
defaultMessage: 'Update ship',
}),
},
{
value: 'ships.remove',
title: intl.formatMessage({
id: 'master.logs.ships.remove',
defaultMessage: 'Remove ship',
}),
},
{
value: 'ships.assign_thing',
title: intl.formatMessage({
id: 'master.logs.ships.assign_thing',
defaultMessage: 'Assign thing to ship',
}),
},
{
value: 'ships.assign_user',
title: intl.formatMessage({
id: 'master.logs.ships.assign_user',
defaultMessage: 'Assign user to ship',
}),
},
{
value: 'ships.unassign_thing',
title: intl.formatMessage({
id: 'master.logs.ships.unassign_thing',
defaultMessage: 'Remove thing from ship',
}),
},
{
value: 'ships.unassign_user',
title: intl.formatMessage({
id: 'master.logs.ships.unassign_user',
defaultMessage: 'Remove user from ship',
}),
},
],
},
// Trips
{
title: intl.formatMessage({
id: 'master.logs.trips',
defaultMessage: 'Trips',
}),
value: '0-5',
selectable: false,
children: [
{
value: 'trips.create',
title: intl.formatMessage({
id: 'master.logs.trips.create',
defaultMessage: 'Create new ship',
}),
},
{
value: 'trips.update',
title: intl.formatMessage({
id: 'master.logs.trips.update',
defaultMessage: 'Update ship',
}),
},
{
value: 'trips.remove',
title: intl.formatMessage({
id: 'master.logs.trips.remove',
defaultMessage: 'Remove ship',
}),
},
{
value: 'trips.approve',
title: intl.formatMessage({
id: 'master.logs.trips.approve',
defaultMessage: 'Approve trip',
}),
},
{
value: 'trips.request_approve',
title: intl.formatMessage({
id: 'master.logs.trips.request_approve',
defaultMessage: 'Request approval for trip',
}),
},
{
value: 'trips.unassign_thing',
title: intl.formatMessage({
id: 'master.logs.trips.unassign_thing',
defaultMessage: 'Remove thing from ship',
}),
},
{
value: 'trips.unassign_user',
title: intl.formatMessage({
id: 'master.logs.trips.unassign_user',
defaultMessage: 'Remove user from ship',
}),
},
],
},
];
};
export default LogActions;

View File

@@ -3,312 +3,16 @@ import { apiQueryLogs } from '@/services/master/LogController';
import { apiQueryUsers } from '@/services/master/UserController'; import { apiQueryUsers } from '@/services/master/UserController';
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components'; import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { theme } from 'antd';
import { DatePicker } from 'antd/lib'; import { DatePicker } from 'antd/lib';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useRef } from 'react'; import { useRef } from 'react';
import LogActions from './components/LogActions';
const SystemLogs = () => { const SystemLogs = () => {
const intl = useIntl(); const intl = useIntl();
const tableRef = useRef<ActionType>(); const tableRef = useRef<ActionType>();
const actions = [ const { token } = theme.useToken();
//Alarm
{
title: intl.formatMessage({
id: 'master.logs.things.alarm.confirm',
defaultMessage: 'Alarm confirm',
}),
value: '0-0',
selectable: false,
children: [
{
value: 'things.alarm_confirm',
title: intl.formatMessage({
id: 'master.logs.things.confirm',
defaultMessage: 'Confirm',
}),
},
{
value: 'things.alarm_unconfirm',
title: intl.formatMessage({
id: 'master.logs.things.unconfirm',
defaultMessage: 'Unconfirm',
}),
},
],
},
//Things
{
title: intl.formatMessage({
id: 'master.logs.things',
defaultMessage: 'Things',
}),
value: '0-1',
selectable: false,
children: [
{
value: 'things.create',
title: intl.formatMessage({
id: 'master.logs.things.create',
defaultMessage: 'Create new thing',
}),
},
{
value: 'things.update',
title: intl.formatMessage({
id: 'master.logs.things.update',
defaultMessage: 'Update thing',
}),
},
{
value: 'things.remove',
title: intl.formatMessage({
id: 'master.logs.things.remove',
defaultMessage: 'Remove thing',
}),
},
{
value: 'things.share',
title: intl.formatMessage({
id: 'master.logs.things.share',
defaultMessage: 'Share thing',
}),
},
{
value: 'things.unshare',
title: intl.formatMessage({
id: 'master.logs.things.unshare',
defaultMessage: 'Unshare thing',
}),
},
{
value: 'things.update_key',
title: intl.formatMessage({
id: 'master.logs.things.update_key',
defaultMessage: 'Update key thing',
}),
},
],
},
// Users
{
title: intl.formatMessage({
id: 'master.logs.users',
defaultMessage: 'Users',
}),
value: '0-2',
selectable: false,
children: [
{
value: 'users.create',
title: intl.formatMessage({
id: 'master.logs.users.create',
defaultMessage: 'Register user',
}),
},
{
value: 'users.update',
title: intl.formatMessage({
id: 'master.logs.users.update',
defaultMessage: 'Update user',
}),
},
{
value: 'users.remove',
title: intl.formatMessage({
id: 'master.logs.users.remove',
defaultMessage: 'Remove user',
}),
},
{
value: 'users.login',
title: intl.formatMessage({
id: 'master.logs.users.login',
defaultMessage: 'User login',
}),
},
],
},
// Groups
{
title: intl.formatMessage({
id: 'master.logs.groups',
defaultMessage: 'Groups',
}),
value: '0-3',
selectable: false,
children: [
{
value: 'group.create',
title: intl.formatMessage({
id: 'master.logs.groups.create',
defaultMessage: 'Create new group',
}),
},
{
value: 'group.update',
title: intl.formatMessage({
id: 'master.logs.groups.update',
defaultMessage: 'Update group',
}),
},
{
value: 'group.remove',
title: intl.formatMessage({
id: 'master.logs.groups.remove',
defaultMessage: 'Remove group',
}),
},
{
value: 'group.assign_thing',
title: intl.formatMessage({
id: 'master.logs.groups.assign_thing',
defaultMessage: 'Assign thing to group',
}),
},
{
value: 'group.assign_user',
title: intl.formatMessage({
id: 'master.logs.groups.assign_user',
defaultMessage: 'Assign user to group',
}),
},
{
value: 'group.unassign_thing',
title: intl.formatMessage({
id: 'master.logs.groups.unassign_thing',
defaultMessage: 'Remove thing from group',
}),
},
{
value: 'group.unassign_user',
title: intl.formatMessage({
id: 'master.logs.groups.unassign_user',
defaultMessage: 'Remove user from group',
}),
},
],
},
// Ships
{
title: intl.formatMessage({
id: 'master.logs.ships',
defaultMessage: 'Ships',
}),
value: '0-4',
selectable: false,
children: [
{
value: 'ships.create',
title: intl.formatMessage({
id: 'master.logs.ships.create',
defaultMessage: 'Create new ship',
}),
},
{
value: 'ships.update',
title: intl.formatMessage({
id: 'master.logs.ships.update',
defaultMessage: 'Update ship',
}),
},
{
value: 'ships.remove',
title: intl.formatMessage({
id: 'master.logs.ships.remove',
defaultMessage: 'Remove ship',
}),
},
{
value: 'ships.assign_thing',
title: intl.formatMessage({
id: 'master.logs.ships.assign_thing',
defaultMessage: 'Assign thing to ship',
}),
},
{
value: 'ships.assign_user',
title: intl.formatMessage({
id: 'master.logs.ships.assign_user',
defaultMessage: 'Assign user to ship',
}),
},
{
value: 'ships.unassign_thing',
title: intl.formatMessage({
id: 'master.logs.ships.unassign_thing',
defaultMessage: 'Remove thing from ship',
}),
},
{
value: 'ships.unassign_user',
title: intl.formatMessage({
id: 'master.logs.ships.unassign_user',
defaultMessage: 'Remove user from ship',
}),
},
],
},
// Trips
{
title: intl.formatMessage({
id: 'master.logs.trips',
defaultMessage: 'Trips',
}),
value: '0-5',
selectable: false,
children: [
{
value: 'trips.create',
title: intl.formatMessage({
id: 'master.logs.trips.create',
defaultMessage: 'Create new ship',
}),
},
{
value: 'trips.update',
title: intl.formatMessage({
id: 'master.logs.trips.update',
defaultMessage: 'Update ship',
}),
},
{
value: 'trips.remove',
title: intl.formatMessage({
id: 'master.logs.trips.remove',
defaultMessage: 'Remove ship',
}),
},
{
value: 'trips.approve',
title: intl.formatMessage({
id: 'master.logs.trips.approve',
defaultMessage: 'Approve trip',
}),
},
{
value: 'trips.request_approve',
title: intl.formatMessage({
id: 'master.logs.trips.request_approve',
defaultMessage: 'Request approval for trip',
}),
},
{
value: 'trips.unassign_thing',
title: intl.formatMessage({
id: 'master.logs.trips.unassign_thing',
defaultMessage: 'Remove thing from ship',
}),
},
{
value: 'trips.unassign_user',
title: intl.formatMessage({
id: 'master.logs.trips.unassign_user',
defaultMessage: 'Remove user from ship',
}),
},
],
},
];
const queryUserSource = async (): Promise<MasterModel.ProfileResponse[]> => { const queryUserSource = async (): Promise<MasterModel.ProfileResponse[]> => {
try { try {
@@ -345,6 +49,10 @@ const SystemLogs = () => {
return ( return (
<DatePicker.RangePicker <DatePicker.RangePicker
width="50%" width="50%"
style={{
backgroundColor: token.colorBgContainer,
color: token.colorText,
}}
presets={[ presets={[
{ {
label: intl.formatMessage({ label: intl.formatMessage({
@@ -404,9 +112,9 @@ const SystemLogs = () => {
treeCheckable: true, treeCheckable: true,
multiple: true, multiple: true,
}, },
request: async () => actions, request: async () => LogActions(intl),
render: (_, item) => { render: (_, item) => {
const childs = actions.flatMap((a) => a.children || []); const childs = LogActions(intl).flatMap((a) => a.children || []);
const action = childs.find((a) => a?.value === item.subtopic); const action = childs.find((a) => a?.value === item.subtopic);
return action?.title ?? '...'; return action?.title ?? '...';
}, },

View File

@@ -1,7 +1,35 @@
import { ActionType, ProList } from '@ant-design/pro-components'; import ThingsFilter from '@/components/shared/ThingFilterModal';
import { useIntl } from '@umijs/max'; import { DEFAULT_PAGE_SIZE } from '@/constants';
import { message } from 'antd'; import {
import { useRef } from 'react'; apiDeleteUserThingPolicy,
apiGetThingPolicyByUser,
apiShareThingToUser,
} from '@/services/master/ThingController';
import { DeleteOutlined, ShareAltOutlined } from '@ant-design/icons';
import {
ActionType,
FooterToolbar,
ProList,
ProListMetas,
} from '@ant-design/pro-components';
import { FormattedMessage, useIntl } from '@umijs/max';
import {
Button,
Checkbox,
GetProp,
message,
Popconfirm,
Tag,
Typography,
} from 'antd';
import { useRef, useState } from 'react';
const { Paragraph } = Typography;
type PolicyShareDefault = {
read: boolean;
write: boolean;
delete: boolean;
};
type ShareThingProps = { type ShareThingProps = {
user: MasterModel.ProfileResponse | null; user: MasterModel.ProfileResponse | null;
@@ -9,56 +37,287 @@ type ShareThingProps = {
const ShareThing = ({ user }: ShareThingProps) => { const ShareThing = ({ user }: ShareThingProps) => {
const listActionRef = useRef<ActionType>(); const listActionRef = useRef<ActionType>();
const intl = useIntl(); const intl = useIntl();
const [messageAPI, contextHolder] = message.useMessage(); const [messageApi, contextHolder] = message.useMessage();
const [selectedRowsState, setSelectedRows] = useState<
MasterModel.ThingPolicy[]
>([]);
const [thingPolicy, setThingPolicy] = useState<MasterModel.ThingPolicy[]>([]);
const [shareThingModalVisible, setShareThingModalVisible] =
useState<boolean>(false);
const [policyShare, setPolicyShare] = useState<PolicyShareDefault>({
read: true,
write: true,
delete: true,
});
const getPolicyInfo = (
policy: MasterModel.Policy,
): { color: string; text: string } => {
switch (policy) {
case 'read':
return {
color: 'blue',
text: intl.formatMessage({
id: 'master.users.things.relation.read',
defaultMessage: 'Read',
}),
};
case 'write':
return {
color: 'gold',
text: intl.formatMessage({
id: 'master.users.things.relation.write',
defaultMessage: 'Write',
}),
};
case 'delete':
return {
color: 'red',
text: intl.formatMessage({
id: 'master.users.things.relation.delete',
defaultMessage: 'Delete',
}),
};
default:
return { color: 'default', text: policy };
}
};
const columns: ProListMetas<MasterModel.ThingPolicy> = {
title: {
dataIndex: 'name',
render: (_, record: MasterModel.ThingPolicy) => (
<Paragraph copyable>{record?.thing_name}</Paragraph>
),
},
subTitle: {
dataIndex: 'metadata.external_id',
render: (_, record: MasterModel.ThingPolicy) => (
<Paragraph copyable>{record?.external_id}</Paragraph>
),
},
description: {
dataIndex: 'policies',
render: (_, record: MasterModel.ThingPolicy) => {
return record?.policies?.map((policy) => {
const info = getPolicyInfo(policy);
return (
<Tag key={policy} color={info.color}>
{info.text}
</Tag>
);
});
},
},
};
const handleShareThings = async (thingIds: string[]) => {
const thingsFiltered = thingIds.filter((thingId) => {
return !thingPolicy.find((thing) => thing.thing_id === thingId);
});
if (thingsFiltered.length === 0) return;
const key = 'share';
try {
messageApi.open({
type: 'loading',
content: intl.formatMessage({
id: 'master.users.things.sharing',
defaultMessage: 'Sharing devices...',
}),
key,
});
const allShare = thingsFiltered.map(async (thingId) => {
const resp = await apiShareThingToUser(
thingId,
user?.id || '',
Object.keys(policyShare).filter(
(key) => policyShare[key as keyof PolicyShareDefault],
),
);
});
await Promise.all(allShare);
messageApi.open({
type: 'success',
content: intl.formatMessage({
id: 'master.users.thing.share.success',
defaultMessage: 'Share successfully and will refresh soon',
}),
key,
});
await new Promise((resolve) => {
setTimeout(resolve, 500);
});
if (listActionRef.current) {
listActionRef.current.reload();
}
} catch (error) {
console.error('Error when share thing: ', error);
messageApi.open({
type: 'error',
content: intl.formatMessage({
id: 'master.users.thing.share.fail',
defaultMessage: 'Share failed, please try again!',
}),
key,
});
}
};
const onChange: GetProp<typeof Checkbox.Group, 'onChange'> = (
checkedValues,
) => {
setPolicyShare({
read: checkedValues.includes('read'),
write: checkedValues.includes('write'),
delete: checkedValues.includes('delete'),
});
};
const handleUnshare = async (selectedRows: MasterModel.ThingPolicy[]) => {
if (!selectedRows) return true;
const key = 'unshare';
try {
messageApi.open({
type: 'loading',
content: intl.formatMessage({
id: 'master.users.things.unsharing',
defaultMessage: 'Unsharing devices...',
}),
key,
});
const allUnshare = selectedRows.map(async (row) => {
const resp = await apiDeleteUserThingPolicy(
row?.thing_id || '',
user?.id || '',
);
});
await Promise.all(allUnshare);
messageApi.open({
type: 'success',
content: intl.formatMessage({
id: 'master.users.thing.unshare.success',
defaultMessage: 'Unshare successfully and will refresh soon',
}),
key,
});
await new Promise((resolve) => {
setTimeout(resolve, 500);
});
return true;
} catch (error) {
console.error('Error when unshare thing: ', error);
messageApi.open({
type: 'error',
content: intl.formatMessage({
id: 'master.users.thing.unshare.fail',
defaultMessage: 'Unshare failed, please try again!',
}),
key,
});
return false;
}
};
return ( return (
<> <>
{contextHolder} {contextHolder}
<ProList <ThingsFilter
isOpen={shareThingModalVisible}
setIsOpen={setShareThingModalVisible}
thingIds={thingPolicy.map((thing) => thing.thing_id!)}
disabled
extra={
<Checkbox.Group
options={[
{
label: intl.formatMessage({
id: 'master.users.things.relation.read',
defaultMessage: 'Read',
}),
value: 'read',
disabled: true,
},
{
label: intl.formatMessage({
id: 'master.users.things.relation.write',
defaultMessage: 'Write',
}),
value: 'write',
},
{
label: intl.formatMessage({
id: 'master.users.things.relation.delete',
defaultMessage: 'Delete',
}),
value: 'delete',
},
]}
value={Object.keys(policyShare).filter(
(key) => policyShare[key as keyof PolicyShareDefault],
)}
onChange={onChange}
/>
}
onSubmit={handleShareThings}
/>
<ProList<MasterModel.ThingPolicy>
headerTitle={intl.formatMessage({ headerTitle={intl.formatMessage({
id: 'pages.users.things.list', id: 'master.users.things.list',
defaultMessage: 'List things', defaultMessage: 'List things',
})} })}
actionRef={listActionRef} actionRef={listActionRef}
toolBarRender={() => [ toolBarRender={() => [
// <Button <Button
// type="primary" type="primary"
// key="primary" key="primary"
// onClick={() => { icon={<ShareAltOutlined />}
// handleShareModalVisible(true); onClick={() => {
// }} setShareThingModalVisible(true);
// > }}
// <PlusOutlined />{" "} >
// <FormattedMessage <FormattedMessage
// id="pages.things.share.text" id="master.users.thing.share.title"
// defaultMessage="Share" defaultMessage="Share"
// /> />
// </Button>, </Button>,
]} ]}
pagination={{
pageSize: DEFAULT_PAGE_SIZE,
}}
metas={columns} metas={columns}
request={async () => { request={async (params) => {
const { current, pageSize } = params;
const query = { const query = {
type: 'sub', type: 'sub',
id: user?.id || '', id: user?.id || '',
}; };
if (user?.id) { if (user?.id) {
const resp = (await apiQueryThingsByPolicy( const offset = current === 1 ? 0 : (current! - 1) * pageSize!;
query, const policyBody: Partial<MasterModel.SearchPaginationBody> = {
)) as PolicyResponse; offset: offset,
const { relations } = resp; limit: pageSize,
if (relations) { };
const queries = relations.map(async (rel: PolicyRelation) => { const resp = await apiGetThingPolicyByUser(policyBody, user.id);
const thg = await apiQueryThing(rel.id);
return { if (resp.things) {
...thg, setThingPolicy(resp.things);
relations: rel?.actions,
};
});
const policies = await Promise.all(queries);
return Promise.resolve({ return Promise.resolve({
success: true, success: true,
data: policies, data: resp.things,
total: policies.length, total: resp.total,
}); });
} else {
return {
success: false,
data: [],
total: 0,
};
} }
} }
return Promise.resolve({ return Promise.resolve({
@@ -67,32 +326,67 @@ const ShareThing = ({ user }: ShareThingProps) => {
total: 0, total: 0,
}); });
}} }}
rowKey="id" rowKey="external_id"
search={false} search={false}
// rowSelection={{ rowSelection={{
// selectedRowKeys: selectedRowsState.map((row) => row.id).filter((id): id is string => id !== undefined), selectedRowKeys: selectedRowsState
// onChange: (_: React.Key[], selectedRows: API.Thing[]) => { .map((row) => row.external_id)
// setSelectedRows(selectedRows); .filter((id): id is string => id !== undefined),
// }, onChange: (_, selectedRows: MasterModel.ThingPolicy[]) => {
// }} setSelectedRows(selectedRows);
/> },
{/* <FormShareVms
visible={shareModalVisibale}
onVisibleChange={handleShareModalVisible}
user={user}
onSubmit={async (values: ShareFormValues) => {
console.log(values);
const success = await handleShare(values);
if (success) {
handleShareModalVisible(false);
onReload();
if (actionRef.current) {
//await delay(1000);
actionRef.current.reload();
}
}
}} }}
/> */} />
{selectedRowsState?.length > 0 && (
<FooterToolbar
extra={
<div>
<FormattedMessage
id="master.footer.chosen"
defaultMessage="Chosen"
/>{' '}
<a
style={{
fontWeight: 600,
}}
>
{selectedRowsState.length}
</a>{' '}
<FormattedMessage
id="common.paginations.things"
defaultMessage="item"
/>
</div>
}
>
<Popconfirm
title={intl.formatMessage({
id: 'master.users.thing.unshare.confirm',
defaultMessage: 'Are you sure to stop sharing these devices?',
})}
okText={intl.formatMessage({
id: 'common.sure',
defaultMessage: 'Sure',
})}
onConfirm={async () => {
const success = await handleUnshare(selectedRowsState);
if (success) {
setSelectedRows([]);
if (listActionRef.current) {
listActionRef.current.reload();
}
}
}}
>
<Button type="primary" danger icon={<DeleteOutlined />}>
<FormattedMessage
id="master.users.thing.unshare.title"
defaultMessage="Sure"
/>
</Button>
</Popconfirm>
</FooterToolbar>
)}
</> </>
); );
}; };

View File

@@ -343,7 +343,7 @@ const ManagerUserPage = () => {
> >
<Popconfirm <Popconfirm
title={intl.formatMessage({ title={intl.formatMessage({
id: 'master.users.deletion.title', id: 'master.users.delete.title',
defaultMessage: 'Are you sure to delete this selected items', defaultMessage: 'Are you sure to delete this selected items',
})} })}
okText={intl.formatMessage({ okText={intl.formatMessage({

View File

@@ -1,4 +1,8 @@
import { API_THINGS_SEARCH } from '@/constants/api'; import {
API_SHARE_THING,
API_THING_POLICY,
API_THINGS_SEARCH,
} from '@/constants/api';
import { request } from '@umijs/max'; import { request } from '@umijs/max';
export async function apiSearchThings( export async function apiSearchThings(
@@ -28,3 +32,43 @@ export async function apiSearchThings(
}); });
} }
} }
export async function apiGetThingPolicyByUser(
params: Partial<MasterModel.SearchPaginationBody>,
userId: string,
) {
return request<MasterModel.ThingPolicyResponse>(
`${API_THING_POLICY}/${userId}`,
{
params: params,
},
);
}
export async function apiDeleteUserThingPolicy(
thing_id: string,
user_id: string,
) {
return request(`${API_SHARE_THING}/${thing_id}/share`, {
method: 'DELETE',
data: {
policies: ['read', 'write', 'delete'],
user_ids: [user_id],
},
getResponse: true,
});
}
export async function apiShareThingToUser(
thing_id: string,
user_id: string,
policies: string[],
) {
return request(`${API_SHARE_THING}/${thing_id}/share`, {
method: 'POST',
data: {
policies: policies,
user_ids: [user_id],
},
getResponse: true,
});
}

View File

@@ -160,21 +160,24 @@ declare namespace MasterModel {
} }
// Thing Policy // Thing Policy
interface ThingPolicyResponse {
total?: number;
offset?: number;
limit?: number;
order?: string;
direction?: string;
metadata?: null;
things?: ThingPolicy[];
}
interface ThingPolicy { interface ThingPolicy {
next_page_token?: string; policies?: Policy[];
relations?: Relation[]; thing_id?: string;
thing_name?: string;
external_id?: string;
} }
interface Relation { type Policy = 'read' | 'delete' | 'write';
id?: string;
actions?: Action[];
}
enum Action {
Delete = 'delete',
Read = 'read',
Write = 'write',
}
// Group // Group