diff --git a/interface/package.json b/interface/package.json
index ff6c3d701..c23ef06f2 100644
--- a/interface/package.json
+++ b/interface/package.json
@@ -30,7 +30,7 @@
"@mui/material": "^5.15.20",
"@table-library/react-table-library": "4.1.7",
"@types/lodash-es": "^4.17.12",
- "@types/node": "^20.14.7",
+ "@types/node": "^20.14.8",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/react-router-dom": "^5.3.3",
diff --git a/interface/src/project/CustomEntities.tsx b/interface/src/project/CustomEntities.tsx
index 9e3402767..77b95fad8 100644
--- a/interface/src/project/CustomEntities.tsx
+++ b/interface/src/project/CustomEntities.tsx
@@ -33,7 +33,7 @@ import { useI18nContext } from 'i18n/i18n-react';
import * as EMSESP from './api';
import SettingsCustomEntitiesDialog from './CustomEntitiesDialog';
import { DeviceValueTypeNames, DeviceValueUOM_s } from './types';
-import type { EntityItem } from './types';
+import type { Entities, EntityItem } from './types';
import { entityItemValidation } from './validators';
const CustomEntities: FC = () => {
@@ -56,7 +56,7 @@ const CustomEntities: FC = () => {
});
const { send: writeEntities } = useRequest(
- (data: { id: number; entity_ids: string[] }) => EMSESP.writeCustomEntities(data),
+ (data: Entities) => EMSESP.writeCustomEntities(data),
{ immediate: false }
);
@@ -236,7 +236,11 @@ const CustomEntities: FC = () => {
return (
!ei.deleted) }}
+ data={{
+ nodes: entities
+ .filter((ei) => !ei.deleted)
+ .sort((a, b) => a.name.localeCompare(b.name))
+ }}
theme={entity_theme}
layout={{ custom: true }}
>
@@ -295,7 +299,7 @@ const CustomEntities: FC = () => {
onClose={onDialogClose}
onSave={onDialogSave}
selectedItem={selectedEntityItem}
- validator={entityItemValidation()}
+ validator={entityItemValidation(entities, selectedEntityItem)}
/>
)}
diff --git a/interface/src/project/CustomEntitiesDialog.tsx b/interface/src/project/CustomEntitiesDialog.tsx
index c9351b80e..1863ca337 100644
--- a/interface/src/project/CustomEntitiesDialog.tsx
+++ b/interface/src/project/CustomEntitiesDialog.tsx
@@ -130,7 +130,8 @@ const CustomEntitiesDialog = ({
{
data={{
nodes: schedule
.filter((si) => !si.deleted)
- .sort((a, b) => a.cmd.localeCompare(b.cmd))
+ .sort((a, b) => a.name.localeCompare(b.name))
}}
theme={schedule_theme}
layout={{ custom: true }}
diff --git a/interface/src/project/api.ts b/interface/src/project/api.ts
index 611e15c08..70438d854 100644
--- a/interface/src/project/api.ts
+++ b/interface/src/project/api.ts
@@ -133,6 +133,7 @@ export const readCustomEntities = () =>
return (data as Entities).entities.map((ei: EntityItem) => ({
...ei,
o_id: ei.id,
+ o_ram: ei.ram,
o_device_id: ei.device_id,
o_type_id: ei.type_id,
o_offset: ei.offset,
@@ -141,9 +142,10 @@ export const readCustomEntities = () =>
o_value_type: ei.value_type,
o_name: ei.name,
o_writeable: ei.writeable,
+ o_value: ei.value,
o_deleted: ei.deleted
}));
}
});
-export const writeCustomEntities = (data: { id: number; entity_ids: string[] }) =>
+export const writeCustomEntities = (data: Entities) =>
alovaInstance.Post('/rest/customEntities', data);
diff --git a/interface/src/project/validators.ts b/interface/src/project/validators.ts
index 3118d18de..168685da6 100644
--- a/interface/src/project/validators.ts
+++ b/interface/src/project/validators.ts
@@ -2,7 +2,13 @@ import Schema from 'async-validator';
import type { InternalRuleItem } from 'async-validator';
import { IP_OR_HOSTNAME_VALIDATOR } from 'validators/shared';
-import type { AnalogSensor, DeviceValue, ScheduleItem, Settings } from './types';
+import type {
+ AnalogSensor,
+ DeviceValue,
+ EntityItem,
+ ScheduleItem,
+ Settings
+} from './types';
export const GPIO_VALIDATOR = {
validator(
@@ -297,10 +303,10 @@ export const schedulerItemValidation = (
) =>
new Schema({
name: [
+ { required: true, message: 'Name is required' },
{
- required: true,
type: 'string',
- pattern: /^[a-zA-Z0-9_\\.]{0,15}$/,
+ pattern: /^[a-zA-Z0-9_\\.]{1,15}$/,
message: "Must be <15 characters: alpha numeric, '_' or '.'"
},
...[uniqueNameValidator(schedule, scheduleItem.o_name)]
@@ -316,7 +322,27 @@ export const schedulerItemValidation = (
]
});
-export const entityItemValidation = () =>
+export const uniqueCustomNameValidator = (
+ entity: EntityItem[],
+ o_name?: string
+) => ({
+ validator(
+ rule: InternalRuleItem,
+ name: string,
+ callback: (error?: string) => void
+ ) {
+ if (
+ (o_name === undefined || o_name !== name) &&
+ entity.find((ei) => ei.name === name)
+ ) {
+ callback('Name already in use');
+ } else {
+ callback();
+ }
+ }
+});
+
+export const entityItemValidation = (entity: EntityItem[], entityItem: EntityItem) =>
new Schema({
name: [
{ required: true, message: 'Name is required' },
@@ -324,7 +350,8 @@ export const entityItemValidation = () =>
type: 'string',
pattern: /^[a-zA-Z0-9_\\.]{1,15}$/,
message: "Must be <15 characters: alpha numeric, '_' or '.'"
- }
+ },
+ ...[uniqueCustomNameValidator(entity, entityItem.o_name)]
],
device_id: [
{
diff --git a/interface/yarn.lock b/interface/yarn.lock
index 2d4d3d0a1..168e6e573 100644
--- a/interface/yarn.lock
+++ b/interface/yarn.lock
@@ -1760,12 +1760,12 @@ __metadata:
languageName: node
linkType: hard
-"@types/node@npm:^20.14.7":
- version: 20.14.7
- resolution: "@types/node@npm:20.14.7"
+"@types/node@npm:^20.14.8":
+ version: 20.14.8
+ resolution: "@types/node@npm:20.14.8"
dependencies:
undici-types: "npm:~5.26.4"
- checksum: 10c0/6211ffe86f769a58617e3bdca58610256021ef54bd99d2c442ee6109196cd6ee8e0bcd1cfac0e39bf4bb353f8900fa5fae540485a03816bd91ad1f86a0e18512
+ checksum: 10c0/06d4643fa3b179b41fe19f9c75c240278ca1f7a313b3b837bc36ea119499c7ad77f06bbe72694ac04aa91ec77fe747baa09b889f4c435450c1724a26bd55f160
languageName: node
linkType: hard
@@ -1996,7 +1996,7 @@ __metadata:
"@trivago/prettier-plugin-sort-imports": "npm:^4.3.0"
"@types/babel__core": "npm:^7"
"@types/lodash-es": "npm:^4.17.12"
- "@types/node": "npm:^20.14.7"
+ "@types/node": "npm:^20.14.8"
"@types/react": "npm:^18.3.3"
"@types/react-dom": "npm:^18.3.0"
"@types/react-router-dom": "npm:^5.3.3"