Compare commits
1 commit
master
...
plenum_amb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d69d8b38f5 |
10 changed files with 264 additions and 8 deletions
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -46,7 +46,7 @@
|
||||||
"mui-tel-input": "^7.0.0",
|
"mui-tel-input": "^7.0.0",
|
||||||
"notistack": "^3.0.1",
|
"notistack": "^3.0.1",
|
||||||
"openweathermap-ts": "^1.2.10",
|
"openweathermap-ts": "^1.2.10",
|
||||||
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#sendgrid_whitelabel",
|
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#plenum_ambient_component",
|
||||||
"query-string": "^9.2.1",
|
"query-string": "^9.2.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-beautiful-dnd": "^13.1.1",
|
"react-beautiful-dnd": "^13.1.1",
|
||||||
|
|
@ -11752,7 +11752,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/protobuf-ts": {
|
"node_modules/protobuf-ts": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#9c0f668d4a56b8216dd71a44c3110a818aaf3541",
|
"resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#1a3b63d5a2f4c9165642d994c842abecc3f3bffc",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"protobufjs": "^6.8.8"
|
"protobufjs": "^6.8.8"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@
|
||||||
"mui-tel-input": "^7.0.0",
|
"mui-tel-input": "^7.0.0",
|
||||||
"notistack": "^3.0.1",
|
"notistack": "^3.0.1",
|
||||||
"openweathermap-ts": "^1.2.10",
|
"openweathermap-ts": "^1.2.10",
|
||||||
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#sendgrid_whitelabel",
|
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#plenum_ambient_component",
|
||||||
"query-string": "^9.2.1",
|
"query-string": "^9.2.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-beautiful-dnd": "^13.1.1",
|
"react-beautiful-dnd": "^13.1.1",
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,10 @@ import {
|
||||||
Voltage,
|
Voltage,
|
||||||
VPD,
|
VPD,
|
||||||
Weight,
|
Weight,
|
||||||
CapacitorCable
|
CapacitorCable,
|
||||||
|
Plenum,
|
||||||
|
AmbientOnewire,
|
||||||
|
AmbientI2C
|
||||||
} from "pbHelpers/ComponentTypes";
|
} from "pbHelpers/ComponentTypes";
|
||||||
//import { multilineGrainCableData } from "pbHelpers/ComponentTypes/GrainCable";
|
//import { multilineGrainCableData } from "pbHelpers/ComponentTypes/GrainCable";
|
||||||
//import { multilineCapCableData } from "./ComponentTypes/CapacitorCable";
|
//import { multilineCapCableData } from "./ComponentTypes/CapacitorCable";
|
||||||
|
|
@ -96,7 +99,11 @@ const COMPONENT_TYPE_MAP = new Map<quack.ComponentType, Function>([
|
||||||
[quack.ComponentType.COMPONENT_TYPE_SEN5X, Sen5x],
|
[quack.ComponentType.COMPONENT_TYPE_SEN5X, Sen5x],
|
||||||
[quack.ComponentType.COMPONENT_TYPE_VIBRATION_CHAIN, VibrationCable],
|
[quack.ComponentType.COMPONENT_TYPE_VIBRATION_CHAIN, VibrationCable],
|
||||||
[quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE, dragerGasDongle],
|
[quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE, dragerGasDongle],
|
||||||
[quack.ComponentType.COMPONENT_TYPE_AIRFLOW, Airflow]
|
[quack.ComponentType.COMPONENT_TYPE_AIRFLOW, Airflow],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_PLENUM, Plenum],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_AMBIENT_ONEWIRE, AmbientOnewire],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_AMBIENT_I2C, AmbientI2C],
|
||||||
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export interface Subtype {
|
export interface Subtype {
|
||||||
|
|
|
||||||
79
src/pbHelpers/ComponentTypes/AmbientI2C.ts
Normal file
79
src/pbHelpers/ComponentTypes/AmbientI2C.ts
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
import TemperatureHumidityDarkIcon from "assets/components/temperatureHumidityDark.png";
|
||||||
|
import TemperatureHumidityLightIcon from "assets/components/temperatureHumidityLight.png";
|
||||||
|
import { convertedUnitMeasurement } from "models/UnitMeasurement";
|
||||||
|
import {
|
||||||
|
AreaChartData,
|
||||||
|
ComponentTypeExtension,
|
||||||
|
GraphFilters,
|
||||||
|
LineChartData,
|
||||||
|
simpleAreaChartData,
|
||||||
|
simpleLineChartData,
|
||||||
|
simpleMeasurements,
|
||||||
|
simpleSummaries,
|
||||||
|
Summary,
|
||||||
|
unitMeasurementSummaries
|
||||||
|
} from "pbHelpers/ComponentType";
|
||||||
|
import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { quack } from "protobuf-ts/quack";
|
||||||
|
|
||||||
|
export function AmbientI2C(subtype: number = 0): ComponentTypeExtension {
|
||||||
|
let temperature = describeMeasurement(
|
||||||
|
quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE,
|
||||||
|
quack.ComponentType.COMPONENT_TYPE_AMBIENT_I2C,
|
||||||
|
subtype
|
||||||
|
);
|
||||||
|
let humidity = describeMeasurement(
|
||||||
|
quack.MeasurementType.MEASUREMENT_TYPE_PERCENT,
|
||||||
|
quack.ComponentType.COMPONENT_TYPE_AMBIENT_I2C,
|
||||||
|
subtype
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
type: quack.ComponentType.COMPONENT_TYPE_AMBIENT_I2C,
|
||||||
|
subtypes: [],
|
||||||
|
friendlyName: "Ambient I2C",
|
||||||
|
description: "Measures temperature and humidity",
|
||||||
|
isController: false,
|
||||||
|
isSource: true,
|
||||||
|
isCalibratable: true,
|
||||||
|
addressTypes: [quack.AddressType.ADDRESS_TYPE_I2C],
|
||||||
|
interactionResultTypes: [],
|
||||||
|
states: [],
|
||||||
|
measurements: simpleMeasurements(temperature, humidity),
|
||||||
|
measurementSummary: async function(measurement: quack.Measurement): Promise<Array<Summary>> {
|
||||||
|
return simpleSummaries(measurement, temperature, humidity);
|
||||||
|
},
|
||||||
|
unitMeasurementSummary: (
|
||||||
|
measurements: convertedUnitMeasurement
|
||||||
|
): Summary[] => {
|
||||||
|
return unitMeasurementSummaries(
|
||||||
|
measurements,
|
||||||
|
quack.ComponentType.COMPONENT_TYPE_AMBIENT_I2C,
|
||||||
|
subtype
|
||||||
|
);
|
||||||
|
},
|
||||||
|
areaChartData: (
|
||||||
|
measurement: pond.UnitMeasurementsForComponent,
|
||||||
|
smoothingAverages?: number,
|
||||||
|
filters?: GraphFilters
|
||||||
|
): AreaChartData => {
|
||||||
|
return simpleAreaChartData(measurement, smoothingAverages, filters);
|
||||||
|
},
|
||||||
|
lineChartData: (
|
||||||
|
measurement: pond.UnitMeasurementsForComponent,
|
||||||
|
smoothingAverages?: number,
|
||||||
|
filters?: GraphFilters
|
||||||
|
): LineChartData => {
|
||||||
|
return simpleLineChartData(
|
||||||
|
quack.ComponentType.COMPONENT_TYPE_AMBIENT_I2C,
|
||||||
|
measurement,
|
||||||
|
smoothingAverages,
|
||||||
|
filters
|
||||||
|
);
|
||||||
|
},
|
||||||
|
minMeasurementPeriodMs: 1000,
|
||||||
|
icon: (theme?: "light" | "dark"): string | undefined => {
|
||||||
|
return theme === "light" ? TemperatureHumidityDarkIcon : TemperatureHumidityLightIcon;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
79
src/pbHelpers/ComponentTypes/AmbientOnewire.ts
Normal file
79
src/pbHelpers/ComponentTypes/AmbientOnewire.ts
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
import TemperatureHumidityDarkIcon from "assets/components/temperatureHumidityDark.png";
|
||||||
|
import TemperatureHumidityLightIcon from "assets/components/temperatureHumidityLight.png";
|
||||||
|
import { convertedUnitMeasurement } from "models/UnitMeasurement";
|
||||||
|
import {
|
||||||
|
AreaChartData,
|
||||||
|
ComponentTypeExtension,
|
||||||
|
GraphFilters,
|
||||||
|
LineChartData,
|
||||||
|
simpleAreaChartData,
|
||||||
|
simpleLineChartData,
|
||||||
|
simpleMeasurements,
|
||||||
|
simpleSummaries,
|
||||||
|
Summary,
|
||||||
|
unitMeasurementSummaries
|
||||||
|
} from "pbHelpers/ComponentType";
|
||||||
|
import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { quack } from "protobuf-ts/quack";
|
||||||
|
|
||||||
|
export function AmbientOnewire(subtype: number = 0): ComponentTypeExtension {
|
||||||
|
let temperature = describeMeasurement(
|
||||||
|
quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE,
|
||||||
|
quack.ComponentType.COMPONENT_TYPE_AMBIENT_ONEWIRE,
|
||||||
|
subtype
|
||||||
|
);
|
||||||
|
let humidity = describeMeasurement(
|
||||||
|
quack.MeasurementType.MEASUREMENT_TYPE_PERCENT,
|
||||||
|
quack.ComponentType.COMPONENT_TYPE_AMBIENT_ONEWIRE,
|
||||||
|
subtype
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
type: quack.ComponentType.COMPONENT_TYPE_AMBIENT_ONEWIRE,
|
||||||
|
subtypes: [],
|
||||||
|
friendlyName: "Onewire Ambient",
|
||||||
|
description: "Measures temperature and humidity",
|
||||||
|
isController: false,
|
||||||
|
isSource: true,
|
||||||
|
isCalibratable: true,
|
||||||
|
addressTypes: [quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY],
|
||||||
|
interactionResultTypes: [],
|
||||||
|
states: [],
|
||||||
|
measurements: simpleMeasurements(temperature, humidity),
|
||||||
|
measurementSummary: async function(measurement: quack.Measurement): Promise<Array<Summary>> {
|
||||||
|
return simpleSummaries(measurement, temperature, humidity);
|
||||||
|
},
|
||||||
|
unitMeasurementSummary: (
|
||||||
|
measurements: convertedUnitMeasurement
|
||||||
|
): Summary[] => {
|
||||||
|
return unitMeasurementSummaries(
|
||||||
|
measurements,
|
||||||
|
quack.ComponentType.COMPONENT_TYPE_AMBIENT_ONEWIRE,
|
||||||
|
subtype
|
||||||
|
);
|
||||||
|
},
|
||||||
|
areaChartData: (
|
||||||
|
measurement: pond.UnitMeasurementsForComponent,
|
||||||
|
smoothingAverages?: number,
|
||||||
|
filters?: GraphFilters
|
||||||
|
): AreaChartData => {
|
||||||
|
return simpleAreaChartData(measurement, smoothingAverages, filters);
|
||||||
|
},
|
||||||
|
lineChartData: (
|
||||||
|
measurement: pond.UnitMeasurementsForComponent,
|
||||||
|
smoothingAverages?: number,
|
||||||
|
filters?: GraphFilters
|
||||||
|
): LineChartData => {
|
||||||
|
return simpleLineChartData(
|
||||||
|
quack.ComponentType.COMPONENT_TYPE_AMBIENT_ONEWIRE,
|
||||||
|
measurement,
|
||||||
|
smoothingAverages,
|
||||||
|
filters
|
||||||
|
);
|
||||||
|
},
|
||||||
|
minMeasurementPeriodMs: 1000,
|
||||||
|
icon: (theme?: "light" | "dark"): string | undefined => {
|
||||||
|
return theme === "light" ? TemperatureHumidityDarkIcon : TemperatureHumidityLightIcon;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
84
src/pbHelpers/ComponentTypes/Plenum.ts
Normal file
84
src/pbHelpers/ComponentTypes/Plenum.ts
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
import TemperatureHumidityDarkIcon from "assets/components/temperatureHumidityDark.png";
|
||||||
|
import TemperatureHumidityLightIcon from "assets/components/temperatureHumidityLight.png";
|
||||||
|
import { convertedUnitMeasurement } from "models/UnitMeasurement";
|
||||||
|
import {
|
||||||
|
AreaChartData,
|
||||||
|
ComponentTypeExtension,
|
||||||
|
GraphFilters,
|
||||||
|
LineChartData,
|
||||||
|
simpleAreaChartData,
|
||||||
|
simpleLineChartData,
|
||||||
|
simpleMeasurements,
|
||||||
|
simpleSummaries,
|
||||||
|
Summary,
|
||||||
|
unitMeasurementSummaries
|
||||||
|
} from "pbHelpers/ComponentType";
|
||||||
|
import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { quack } from "protobuf-ts/quack";
|
||||||
|
|
||||||
|
export function Plenum(subtype: number = 0): ComponentTypeExtension {
|
||||||
|
let temperature = describeMeasurement(
|
||||||
|
quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE,
|
||||||
|
quack.ComponentType.COMPONENT_TYPE_PLENUM,
|
||||||
|
subtype
|
||||||
|
);
|
||||||
|
let humidity = describeMeasurement(
|
||||||
|
quack.MeasurementType.MEASUREMENT_TYPE_PERCENT,
|
||||||
|
quack.ComponentType.COMPONENT_TYPE_PLENUM,
|
||||||
|
subtype
|
||||||
|
);
|
||||||
|
let pressure = describeMeasurement(
|
||||||
|
quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE,
|
||||||
|
quack.ComponentType.COMPONENT_TYPE_PLENUM,
|
||||||
|
subtype
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
type: quack.ComponentType.COMPONENT_TYPE_PLENUM,
|
||||||
|
subtypes: [],
|
||||||
|
friendlyName: "Plenum",
|
||||||
|
description: "Measures temperature, humidity and pressure",
|
||||||
|
isController: false,
|
||||||
|
isSource: true,
|
||||||
|
isCalibratable: true,
|
||||||
|
addressTypes: [quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY],
|
||||||
|
interactionResultTypes: [],
|
||||||
|
states: [],
|
||||||
|
measurements: simpleMeasurements(temperature, humidity, pressure),
|
||||||
|
measurementSummary: async function(measurement: quack.Measurement): Promise<Array<Summary>> {
|
||||||
|
return simpleSummaries(measurement, temperature, humidity, pressure);
|
||||||
|
},
|
||||||
|
unitMeasurementSummary: (
|
||||||
|
measurements: convertedUnitMeasurement
|
||||||
|
): Summary[] => {
|
||||||
|
return unitMeasurementSummaries(
|
||||||
|
measurements,
|
||||||
|
quack.ComponentType.COMPONENT_TYPE_PLENUM,
|
||||||
|
subtype
|
||||||
|
);
|
||||||
|
},
|
||||||
|
areaChartData: (
|
||||||
|
measurement: pond.UnitMeasurementsForComponent,
|
||||||
|
smoothingAverages?: number,
|
||||||
|
filters?: GraphFilters
|
||||||
|
): AreaChartData => {
|
||||||
|
return simpleAreaChartData(measurement, smoothingAverages, filters);
|
||||||
|
},
|
||||||
|
lineChartData: (
|
||||||
|
measurement: pond.UnitMeasurementsForComponent,
|
||||||
|
smoothingAverages?: number,
|
||||||
|
filters?: GraphFilters
|
||||||
|
): LineChartData => {
|
||||||
|
return simpleLineChartData(
|
||||||
|
quack.ComponentType.COMPONENT_TYPE_PLENUM,
|
||||||
|
measurement,
|
||||||
|
smoothingAverages,
|
||||||
|
filters
|
||||||
|
);
|
||||||
|
},
|
||||||
|
minMeasurementPeriodMs: 1000,
|
||||||
|
icon: (theme?: "light" | "dark"): string | undefined => {
|
||||||
|
return theme === "light" ? TemperatureHumidityDarkIcon : TemperatureHumidityLightIcon;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -29,3 +29,6 @@ export * from "./Voltage";
|
||||||
export * from "./VPD";
|
export * from "./VPD";
|
||||||
export * from "./Weight";
|
export * from "./Weight";
|
||||||
export * from "./CapacitorCable";
|
export * from "./CapacitorCable";
|
||||||
|
export * from "./Plenum";
|
||||||
|
export * from "./AmbientOnewire";
|
||||||
|
export * from "./AmbientI2C";
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,8 @@ const DefaultAvailability: DeviceAvailabilityMap = new Map<quack.AddressType, De
|
||||||
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]],
|
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]],
|
||||||
[quack.ComponentType.COMPONENT_TYPE_AIRFLOW, [0x6c]],
|
[quack.ComponentType.COMPONENT_TYPE_AIRFLOW, [0x6c]],
|
||||||
[quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, [0x71]], // the address cables will use when they are plugged into the expander with V2 device only
|
[quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, [0x71]], // the address cables will use when they are plugged into the expander with V2 device only
|
||||||
[quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE, [0x6d]]
|
[quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE, [0x6d]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_AMBIENT_I2C, [0x44]]
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
[quack.AddressType.ADDRESS_TYPE_DAC, [0, 1]],
|
[quack.AddressType.ADDRESS_TYPE_DAC, [0, 1]],
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,8 @@ export const BindaptV2MonitorAvailability: DeviceAvailabilityMap = new Map<
|
||||||
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62, 0x66, 0x10]],
|
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62, 0x66, 0x10]],
|
||||||
[quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]],
|
[quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]],
|
||||||
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]],
|
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]],
|
||||||
[quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, [0x71]]
|
[quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, [0x71]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_AMBIENT_I2C, [0x44]]
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
[quack.AddressType.ADDRESS_TYPE_POWER, [0]],
|
[quack.AddressType.ADDRESS_TYPE_POWER, [0]],
|
||||||
|
|
@ -285,7 +286,8 @@ export const BindaptV2AutomateAvailability: DeviceAvailabilityMap = new Map<
|
||||||
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62, 0x66, 0x10]],
|
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62, 0x66, 0x10]],
|
||||||
[quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]],
|
[quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]],
|
||||||
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]],
|
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]],
|
||||||
[quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, [0x71]]
|
[quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, [0x71]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_AMBIENT_I2C, [0x44]]
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
[quack.AddressType.ADDRESS_TYPE_POWER, [0]],
|
[quack.AddressType.ADDRESS_TYPE_POWER, [0]],
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,7 @@ export default function ObjectUsers(props: Props) {
|
||||||
userAPI
|
userAPI
|
||||||
.listObjectUsers(scope)
|
.listObjectUsers(scope)
|
||||||
.then((response: any) => {
|
.then((response: any) => {
|
||||||
|
console.log(response)
|
||||||
let rUsers: User[] = [];
|
let rUsers: User[] = [];
|
||||||
or(response.data, { users: [] }).users.forEach((user: any) => {
|
or(response.data, { users: [] }).users.forEach((user: any) => {
|
||||||
rUsers.push(User.any(user));
|
rUsers.push(User.any(user));
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue