Skip to content

Commit b780eb4

Browse files
silabs-borislrzr
authored andcommitted
GH-43: Unify_UserCredential cluster integration in Dev-GUI
Bug-SiliconLabs: UIC-3222 Bug-GitHub: #43 Signed-off-by: Philippe Coval <philippe.coval@silabs.com>
1 parent 924445b commit b780eb4

File tree

10 files changed

+775
-2
lines changed

10 files changed

+775
-2
lines changed

applications/dev_ui/dev_gui/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import Scene from './pages/scenes/scene/scene';
3333
import EpScenes from './pages/scenes/ep-scenes/ep-scenes';
3434
import { CommissionableDevices } from './pages/commissionable-devices/commissionable-devices';
3535
import { Button, Modal, Spinner } from 'react-bootstrap';
36+
import UserCredential from './pages/user-credential/user-credential';
3637

3738
class App extends Component<{}, AppState> {
3839
constructor(props: {}) {
@@ -278,6 +279,7 @@ class App extends Component<{}, AppState> {
278279
<Route path='/networkmanagement' exact render={() => <NetworkManagement ref={this.changeNodes} {...baseProps} NodeList={this.state.NodeList} />} />
279280
<Route path='/measurements' exact render={() => <Measurements {...baseProps} NodeList={this.state.NodeList} />} />
280281
<Route path='/commissionabledevices' exact render={() => <CommissionableDevices {...baseProps} List={this.state.CommissionableDevices} />} />
282+
<Route path='/usercredential' exact render={() => <UserCredential ref={this.changeConfParams} {...baseProps} NodeList={this.state.NodeList} />} />
281283
<Redirect from="/" exact to="/nodes" />
282284
{Object.keys(ClusterTypes).map((type, index) =>
283285
<Route key={index} path={NavbarItems.find(i => i.name === type)?.path} render={() =>

applications/dev_ui/dev_gui/src/pages/base-clusters/cluster-view-overrides.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { ClusterTypes } from '../../cluster-types/cluster-types';
1010
import { ClusterViewOverride } from './base-cluster-types';
1111
import { Link } from 'react-router-dom';
1212
import { Tooltip } from '@mui/material';
13-
import { Button } from 'react-bootstrap';
13+
import { Button, Badge } from 'react-bootstrap';
1414

1515
//Here you can find icons that can be used to customize you page: https://react-icons.github.io/react-icons/
1616
//Don`t forgot to check licence if you use something that is not in Licence.txt
@@ -769,4 +769,23 @@ export let ClusterViewOverrides = {
769769
} as NavbarItem,
770770
IsExpandable: true
771771
} as ClusterViewOverride,
772+
773+
UserCredential: {
774+
NodesTooltip: (endpoint: string) =>
775+
<Tooltip title={`Endpoint ${endpoint}: User Credential`}>
776+
<span className="cursor-default">
777+
<Link to={`/usercredential`}>
778+
<RiIcons.RiUserSettingsFill color="#212529" />
779+
</Link>
780+
</span>
781+
</Tooltip>,
782+
NavbarItem: {
783+
name: "User Credential",
784+
title: 'User Credential',
785+
path: '/usercredential',
786+
icon: <RiIcons.RiUserSettingsFill />,
787+
cName: 'nav-text',
788+
subMenu: SideMenu.Actuators
789+
} as NavbarItem
790+
} as ClusterViewOverride,
772791
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export type UserDlgState = {
2+
Command: any,
3+
Unid: any,
4+
ShowModal: boolean,
5+
UserCredential: any
6+
}
7+
8+
export type UserDlgProps = {
9+
SocketServer: WebSocket
10+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import * as React from 'react';
2+
import { Button, Modal } from 'react-bootstrap';
3+
import { UserDlgProps, UserDlgState } from './cred-dlg-types';
4+
import UserCredentialAttrs from '../user-credential-attrs/user-credential-attrs';
5+
6+
7+
class CredDlg extends React.Component<UserDlgProps, UserDlgState> {
8+
constructor(props: UserDlgProps) {
9+
super(props);
10+
this.sendCommand = this.sendCommand.bind(this);
11+
this.toggleModal = this.toggleModal.bind(this);
12+
this.state = {
13+
Command: {},
14+
Unid: "",
15+
ShowModal: false,
16+
UserCredential: {}
17+
};
18+
this.changeCommandAttrs = React.createRef();
19+
}
20+
changeCommandAttrs: any;
21+
22+
toggleModal(value: boolean) {
23+
this.setState({ ShowModal: value });
24+
}
25+
26+
getDefinedUserIDs(userCredential: any) {
27+
var users = userCredential.User
28+
if (!users) {
29+
return [];
30+
}
31+
return Object.keys(users).map(user_id => {
32+
return { label: user_id, id: parseInt(user_id) }
33+
});
34+
}
35+
getAvailableSlots(userCredential: any, currentUser: any, credentialType: any) {
36+
var availableSlot = [];
37+
var currentCredentialRule = userCredential.Credentials[credentialType] || {};
38+
var maxSlot = currentCredentialRule.SupportedSlotCount?.Reported || 255;
39+
var currentUserSlots = userCredential.User[currentUser]?.Credential[credentialType] || {};
40+
41+
for (let i = 1; i <= maxSlot; i++) {
42+
if (currentUserSlots[i] === undefined) {
43+
availableSlot.push({label: i +"", id: i});
44+
}
45+
}
46+
47+
return availableSlot;
48+
}
49+
getSupportedEnum(enumData: any, supportedEnumField: any) {
50+
if (!supportedEnumField) {
51+
return enumData;
52+
}
53+
return enumData.filter((enumItem:any) => supportedEnumField[enumItem.name]);
54+
}
55+
56+
updateState(unid: string, command: any, showModal: boolean, userCredential: any) {
57+
let updatedCommand = structuredClone(command);
58+
59+
updatedCommand.fields = command.fields.map( (field: any) => {
60+
switch (field.name) {
61+
case "UserUniqueID":
62+
field.values = this.getDefinedUserIDs(userCredential);
63+
if (field.values.length !== 0) {
64+
field.defaultValue = field.values[0];
65+
field.default = field.values[0].id;
66+
}
67+
break;
68+
case "CredentialType":
69+
field.enum = this.getSupportedEnum(field.enum, userCredential.SupportedCredentialTypes?.Reported);
70+
break;
71+
72+
// case "CredentialSlot":
73+
// field.values = this.getAvailableSlots(userCredential, updatedCommand.fields["UserUniqueID"], updatedCommand.fields["CredentialType"]);
74+
// if (field.values.length !== 0) {
75+
// field.defaultValue = field.values[0];
76+
// field.default = field.values[0].id;
77+
// }
78+
// break;
79+
}
80+
return field;
81+
});
82+
83+
84+
updatedCommand.UserList = userCredential.User;
85+
86+
this.setState({ Unid: unid, Command: updatedCommand, ShowModal: showModal, UserCredential: userCredential },
87+
() => {
88+
this.changeCommandAttrs.current.updateState(this.state.Command)
89+
});
90+
91+
}
92+
93+
sendCommand() {
94+
if (this.state.UserCredential !== undefined)
95+
this.props.SocketServer.send(JSON.stringify(
96+
{
97+
type: "run-cluster-command",
98+
data: {
99+
Unid: this.state.Unid,
100+
ClusterType: "UserCredential",
101+
Cmd: this.state.Command.name,
102+
Payload: this.changeCommandAttrs.current.state.Payload
103+
}
104+
}));
105+
}
106+
107+
render() {
108+
return (
109+
<Modal show={this.state.ShowModal} size="lg" onHide={() => this.toggleModal(false)}>
110+
<Modal.Header closeButton>
111+
<Modal.Title>{this.state.Command.name}</Modal.Title>
112+
</Modal.Header>
113+
<Modal.Body>
114+
<UserCredentialAttrs ref={this.changeCommandAttrs} />
115+
</Modal.Body>
116+
<Modal.Footer>
117+
<Button variant="primary" onClick={() => { this.sendCommand(); this.toggleModal(false); }}>
118+
Send
119+
</Button>
120+
<Button variant="outline-primary" onClick={() => this.toggleModal(false)}>
121+
Cancel
122+
</Button>
123+
</Modal.Footer>
124+
</Modal>
125+
);
126+
}
127+
}
128+
129+
export default CredDlg
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import * as React from 'react';
2+
import { Card, Form } from 'react-bootstrap';
3+
import * as FiIcons from 'react-icons/fi';
4+
import { Autocomplete, FormControlLabel, InputAdornment, MenuItem, Switch, TextField, Tooltip } from '@mui/material';
5+
import CommandAttrs from '../../../components/command-atts/command-attrs';
6+
7+
class UserCredentialAttrs extends CommandAttrs {
8+
constructor(props: {}) {
9+
super(props);
10+
}
11+
12+
13+
// If the command is trying to modify a user, we update the payload with the user's data
14+
// Otherwise we only update the payload UserUniqueID
15+
updatePayloadBaseOnUserData(userID : any, payload: any, commandName: string, userList: any) {
16+
let modifyCommand = commandName.includes("Modify");
17+
18+
payload["UserUniqueID"] = userID;
19+
20+
if (modifyCommand) {
21+
let user = userList[userID];
22+
Object.keys(payload).forEach((key) => {
23+
if (user[key]) {
24+
payload[key] = user[key].Reported;
25+
}
26+
});
27+
}
28+
29+
return payload;
30+
}
31+
32+
// Called when the User ID changes in the modal
33+
onValueChange(userIdObject: any, name: string) {
34+
if (!userIdObject) {
35+
return;
36+
}
37+
let commandName = this.state.Command?.name;
38+
switch(name) {
39+
case "UserUniqueID":
40+
let userList = this.state.Command?.UserList;
41+
if (commandName && userList) {
42+
let newPayload = this.updatePayloadBaseOnUserData(userIdObject.id, this.state.Payload, commandName, userList);
43+
this.setState({ Payload: newPayload });
44+
}
45+
break;
46+
}
47+
48+
49+
}
50+
51+
// This is the default payload for the command
52+
// We update it if we are modifying something
53+
getPayload(command: any) {
54+
let payload = super.getPayload(command);
55+
return this.updatePayloadBaseOnUserData(payload.UserUniqueID, payload, command.name, command.UserList);
56+
}
57+
58+
renderField = (item: any, payload: any, prefixNames: any[], index: any) => {
59+
var validationRegex = ""
60+
// Expire timeout minutes is only for DuressUser
61+
if (item.name === "ExpiringTimeoutMinutes" && this.state.Payload?.UserType !== "DuressUser") {
62+
return;
63+
}
64+
if (item.name === "CredentialData" && this.state.Payload?.CredentialType === "PINCode") {
65+
validationRegex = "^[0-9]*$";
66+
}
67+
switch (item.type) {
68+
case "number":
69+
if (item.values) {
70+
return (
71+
<div key={index} className="col-sm-6 inline margin-v-10">
72+
<Autocomplete
73+
disablePortal
74+
options={item.values}
75+
defaultValue={item.defaultValue}
76+
onChange={(_,value) => this.onValueChange(value, item.name)}
77+
renderInput={(params) => <TextField {...params} className="flex-input" fullWidth={true} label={item.name} name={item.name}
78+
variant="outlined"
79+
/>}
80+
/>
81+
</div>
82+
)
83+
}
84+
break;
85+
case "boolean":
86+
return (
87+
<div key={index} className="col-sm-6 inline margin-v-10">
88+
<div className="col-sm-12">
89+
<div className="check-container">
90+
<FormControlLabel control={<Switch checked={payload} />} label={item.name} name={item.name} onChange={this.handleChange.bind(this, prefixNames, true, false)} />
91+
</div>
92+
</div>
93+
</div>
94+
)
95+
case "enum":
96+
if (item.enum && item.enum.length) {
97+
return (
98+
<div key={index} className="col-sm-6 inline margin-v-10">
99+
<TextField size="small" className="flex-input" fullWidth={true} select label={item.name} name={item.name}
100+
value={payload} onChange={this.handleChange.bind(this, prefixNames, false, false)} variant="outlined">
101+
{item.enum.map((j: any, ind: number) => {
102+
return <MenuItem key={ind} value={j.name}>
103+
{j.name}
104+
</MenuItem>
105+
})}
106+
</TextField>
107+
</div>
108+
)
109+
}
110+
break;
111+
}
112+
return (<div key={index} className={`col-sm-6 inline margin-v-10`}>
113+
<TextField size="small" className="flex-input" fullWidth={true} label={item.name} name={item.name} variant="outlined" type={item.type}
114+
value={this.getValue(item.type, payload)}
115+
onChange={ (event: any) => {
116+
if (validationRegex !== "") {
117+
if (!event.target.value.match(validationRegex)) {
118+
return;
119+
}
120+
}
121+
if (item.maxLength && item.maxLength < event.target.value.length) {
122+
return
123+
}
124+
this.handleChange(prefixNames, false, item.type === "number", event)
125+
}}
126+
onFocus={(event: any) => event.target.select()} inputProps={
127+
{ readOnly: this.state.ReadOnly }
128+
} />
129+
</div>)
130+
}
131+
}
132+
133+
export default UserCredentialAttrs
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export type UserCredentialProps = {
2+
NodeList: any,
3+
SocketServer: WebSocket,
4+
IsConnected: boolean | null
5+
}
6+
7+
export type UserCredentialState = {
8+
List: Map<string, any>,
9+
IsAllExpanded: boolean
10+
}

0 commit comments

Comments
 (0)