Skip to content

Commit 19a9b70

Browse files
committed
Added state for instances (add, edit and delete)
1 parent 0a224aa commit 19a9b70

6 files changed

Lines changed: 254 additions & 89 deletions

File tree

ui/src/components/table/Table.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import "./Table.scss";
22

3-
const Cell = ({ content }: { content: any }) => {
3+
export const Cell = ({ content }: { content: any }) => {
44
return <td className="tableCell">{content}</td>;
55
};
66

@@ -20,7 +20,7 @@ const Headers = ({ headers }: { headers: any }) => {
2020
);
2121
};
2222

23-
const Row = ({ cells }: { cells: any }) => {
23+
export const Row = ({ cells }: { cells: any }) => {
2424
return (
2525
<tr className="tableRow">
2626
{cells.map((content: any, ind: Number) => {
@@ -30,22 +30,23 @@ const Row = ({ cells }: { cells: any }) => {
3030
);
3131
};
3232

33-
const Body = ({ rows }: { rows: any[] }) => {
33+
const Body = ({ rows, children }: { rows: any[]; children: any[] }) => {
3434
return (
3535
<tbody>
3636
{rows.map((cells: any[], ind: Number) => {
3737
return <Row cells={cells} />;
3838
})}
39+
{children}
3940
</tbody>
4041
);
4142
};
4243

43-
const Table = ({ data }: { data: any }) => {
44+
export const Table = ({ data, rows }: { data: any; rows?: any }) => {
4445
return (
4546
<div className="table-container">
4647
<table>
4748
<Headers headers={data.headers} />
48-
<Body rows={data.rows} />
49+
<Body rows={data.rows}>{rows}</Body>
4950
</table>
5051
</div>
5152
);

ui/src/recoil/atoms/instances.tsx

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,51 @@
1-
import { atom } from "recoil";
1+
import { atom, atomFamily, selectorFamily } from "recoil";
2+
import { Profile } from "./profiles";
3+
import { recoilPersist } from "recoil-persist";
24

3-
export const instances = atom({
4-
key: "instancesList",
5+
const { persistAtom } = recoilPersist();
6+
7+
export type Instance = {
8+
id: number | undefined;
9+
name: string;
10+
host: string;
11+
description: string;
12+
profile: Profile | undefined;
13+
};
14+
15+
export const instanceIds = atom<number[]>({
16+
key: "instanceIds",
517
default: [],
18+
effects_UNSTABLE: [persistAtom],
19+
});
20+
21+
export const DefaultInstance = {
22+
id: undefined,
23+
name: "",
24+
host: "",
25+
description: "",
26+
profile: undefined,
27+
};
28+
29+
export const instances = atomFamily<Instance, number>({
30+
key: "instance",
31+
default: DefaultInstance,
32+
effects_UNSTABLE: [persistAtom],
33+
});
34+
35+
export const intanceFormErrors = atom({
36+
key: "intanceFormErrors",
37+
default: {} as { [key: string]: string },
38+
});
39+
40+
export const intanceFormFieldErrors = selectorFamily({
41+
key: "profileFormFieldErrors",
42+
get:
43+
(field: string) =>
44+
({ get }) =>
45+
get(intanceFormErrors)[field],
46+
});
47+
48+
export const instanceFormFields = atom({
49+
key: "instanceFormFields",
50+
default: DefaultInstance as { [key: string]: any },
651
});

ui/src/routes/instances/InstanceForm.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,38 @@ type InstanceFormProps = {
77
onSubmit: any;
88
};
99

10+
export const InstanceFormFields = [
11+
{
12+
name: "name",
13+
help: "Name of the instance",
14+
fieldattrs: {
15+
type: "text",
16+
required: true,
17+
placeholder: "Type a name for the instance",
18+
},
19+
},
20+
{
21+
name: "description",
22+
help: "Instance description",
23+
fieldattrs: {
24+
type: "text",
25+
required: false,
26+
placeholder: "Add a short description for the instance",
27+
as: "textarea",
28+
},
29+
},
30+
{
31+
name: "host",
32+
help: "Host in where the instance can be reached. E.g.: localhost, 0.0.0.0, etc.",
33+
readOnly: true,
34+
fieldattrs: {
35+
type: "text",
36+
required: true,
37+
placeholder: "localhost",
38+
},
39+
},
40+
];
41+
1042
const InstanceForm = (props: InstanceFormProps) => {
1143
// TODO: Perhaps repalce this in the future with an API call to get the fields and their structure
1244
let fields = [

ui/src/routes/instances/Instances.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import { InstancesTable } from "./InstancesTable";
55
import { InstancesUtils } from "./InstancesUtils";
66
import InstancesHeader from "./InstancesHeader";
77

8-
const Page = "Instances";
9-
108
/*
119
* This component is to create line charts.
1210
* Enable and modify this component to add visual information regarding
@@ -38,7 +36,7 @@ const Instances = () => {
3836
return (
3937
<main>
4038
<InstancesHeader view="Instances" />
41-
<InstancesUtils view={Page} />
39+
<InstancesUtils />
4240
<SearchBar filter="instances" />
4341
<InstancesTable />
4442
</main>

ui/src/routes/instances/InstancesTable.tsx

Lines changed: 91 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,24 @@ import {
77
} from "react-icons/ai";
88
import { BsArrowRepeat, BsGlobe } from "react-icons/bs";
99
import { FaNetworkWired } from "react-icons/fa";
10+
import { useRecoilCallback, useRecoilState, useRecoilValue } from "recoil";
11+
import { SimpleForm } from "../../components/forms/Form";
1012
import { Pop } from "../../components/pop/Pop";
11-
import Table from "../../components/table/Table";
13+
import { Table, Row } from "../../components/table/Table";
1214
import {
1315
DeleteDropdownItem,
16+
EditDropdownItem,
1417
InteractionBadge,
1518
OptionsDropdown,
1619
} from "../../components/utils/Common";
20+
import { getPage, InteractionOption } from "../../constants/globals";
1721
import {
18-
getPage,
19-
InteractionOption,
20-
InteractionOptions,
21-
} from "../../constants/globals";
22+
Instance,
23+
instanceIds,
24+
instances,
25+
intanceFormFieldErrors,
26+
} from "../../recoil/atoms/instances";
27+
import { InstanceFormFields } from "./InstanceForm";
2228

2329
interface InstanceService {
2430
name: string;
@@ -27,11 +33,34 @@ interface InstanceService {
2733
running: boolean;
2834
}
2935

30-
interface Instance {
31-
name: string;
32-
services: InstanceService[];
33-
address?: string;
34-
}
36+
const EditInstance = ({ instance }: { instance: Instance }) => {
37+
const pageName = "Instances";
38+
// Get the page to set the icon
39+
const page = getPage(pageName);
40+
41+
// Create a setter for the submit
42+
const id = instance.id !== undefined ? instance.id : -1;
43+
const onSubmit = useRecoilCallback(({ set }) => (instance: Instance) => {
44+
set(instances(id), (prev) => ({ ...prev, ...instance }));
45+
});
46+
47+
// Create the form with the default values as they currently are
48+
const content = (
49+
<SimpleForm
50+
create={false}
51+
defaultValues={instance}
52+
errors={intanceFormFieldErrors}
53+
onSubmit={onSubmit}
54+
page={pageName}
55+
fields={InstanceFormFields}
56+
/>
57+
);
58+
59+
// Get the form with the update tag
60+
return (
61+
<EditDropdownItem form={content} icon={page?.icon} title={"profile"} />
62+
);
63+
};
3564

3665
const ProxyInfoPop = ({ proxy }: { proxy: Number }) => {
3766
return (
@@ -74,7 +103,13 @@ const InstanceRowInfoPop = ({ services }: { services: InstanceService[] }) => {
74103
);
75104
};
76105

77-
const InstanceRowInfo = ({ name, services }: Instance) => {
106+
const InstanceRowInfo = ({
107+
name,
108+
services,
109+
}: {
110+
name: string;
111+
services: InstanceService[];
112+
}) => {
78113
return (
79114
<>
80115
<div>{name}</div>
@@ -83,7 +118,13 @@ const InstanceRowInfo = ({ name, services }: Instance) => {
83118
);
84119
};
85120

86-
const InstanceRowAddress = ({ value }: { value?: string }) => {
121+
const InstanceRowAddress = ({ id }: { id: number }) => {
122+
const [instance, setter] = useRecoilState(instances(id));
123+
const [value, setValue] = useState(instance.host);
124+
const onClick = () => {
125+
setter({ ...instance, host: value });
126+
};
127+
87128
return (
88129
<Col xs="8">
89130
<InputGroup size="sm" className="address">
@@ -95,63 +136,74 @@ const InstanceRowAddress = ({ value }: { value?: string }) => {
95136
aria-label="Instance's address"
96137
aria-describedby="basic-addon2"
97138
value={value}
139+
onChange={(e) => setValue(e.target.value)}
98140
/>
99-
<Button variant="outline-secondary" id="button-addon2">
141+
<Button
142+
variant="outline-secondary"
143+
id="button-addon2"
144+
onClick={onClick}
145+
>
100146
<BsArrowRepeat />
101147
</Button>
102148
</InputGroup>
103149
</Col>
104150
);
105151
};
106152

107-
const InstanceRowOptions = ({ name }: { name: string }) => {
153+
const InstanceRowOptions = ({ instance }: { instance: Instance }) => {
154+
const ids = useRecoilValue(instanceIds);
155+
156+
const deleteCallback = useRecoilCallback(
157+
({ set }) =>
158+
(id: number | undefined) => {
159+
set(
160+
instanceIds,
161+
ids.filter((curr) => curr !== id)
162+
);
163+
}
164+
);
165+
108166
const page = getPage("Instances");
109167
const note =
110168
"Instance services will be stopped and removed from the instance register.";
111169
return (
112170
<OptionsDropdown>
171+
<EditInstance instance={instance} />
113172
{page && (
114173
<DeleteDropdownItem
115-
onClick={() => {
116-
return;
117-
}}
118174
page={page}
119175
note={note}
120-
name={name}
176+
name={instance.name}
177+
onClick={() => deleteCallback(instance.id)}
121178
/>
122179
)}
123180
</OptionsDropdown>
124181
);
125182
};
126183

127-
const InstanceRow = ({ name, address, services }: Instance) => {
128-
return [
129-
<InstanceRowInfo name={name} services={services} />,
130-
<InstanceRowAddress value={address} />,
131-
<InstanceRowOptions name={name} />,
184+
const InstanceRow = ({ id }: { id: number }) => {
185+
const instance = useRecoilValue(instances(id));
186+
const services: InstanceService[] = [];
187+
188+
const cells = [
189+
<InstanceRowInfo name={instance.name} services={services} />,
190+
<InstanceRowAddress id={id} />,
191+
<InstanceRowOptions instance={instance} />,
132192
];
193+
194+
return <Row cells={cells} />;
133195
};
134196

135197
export const InstancesTable = () => {
136-
const props: Instance = {
137-
name: "Lab1",
138-
address: "127.0.0.11",
139-
services: [
140-
{
141-
name: "CoAP",
142-
proxy: 5683,
143-
interaction: InteractionOptions[0],
144-
running: true,
145-
},
146-
],
147-
};
148-
149-
const rows = [InstanceRow(props), InstanceRow(props)];
198+
const insts = useRecoilValue(instanceIds);
199+
const rows = insts.map((instance, index) => (
200+
<InstanceRow key={index} id={instance} />
201+
));
150202

151203
const data = {
152204
headers: [`${rows.length} Instances`, "", ""],
153-
rows: rows,
205+
rows: [],
154206
};
155207

156-
return <Table data={data} />;
208+
return <Table data={data} rows={rows} />;
157209
};

0 commit comments

Comments
 (0)