Skip to content

Commit 8027bc6

Browse files
authored
Merge pull request #447 from IndustryFusion/abhijith-changes
Adds secondary config area
2 parents eea4987 + d020ed5 commit 8027bc6

10 files changed

Lines changed: 673 additions & 177 deletions

File tree

frontend/public/locales/de/dashboard.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
"ip_address": "Verbindungszeichenfolge",
9191
"main_topic": "Haupttopic",
9292
"protocol": "Protokoll",
93-
"app_config": "App-Konfiguration",
93+
"app_config": "Primäre Anwendungskonfiguration",
9494
"pod_name": "Pod-Name",
9595
"hostname": "Hostname",
9696
"port": "Port",

frontend/public/locales/en/dashboard.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
"ip_address": "Connection String",
9191
"main_topic": "Main Topic",
9292
"protocol": "Protocol",
93-
"app_config": "App Config",
93+
"app_config": "Primary Application Configuration",
9494
"pod_name": "Pod Name",
9595
"hostname": "Hostname",
9696
"port": "Port",

frontend/src/components/dashboard/dashboard-assets.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ const DashboardAssets: React.FC<DashboardAssetsProps> = ({ setBlockerProp, setPr
6060
successToast: false
6161
})
6262
const [onboardAsset, setOnboardAsset] = useState(false)
63+
const [isOnboarded, setIsOnboarded] = useState(false)
6364
const [selectedRow, setSelectedRow] = useState<Asset | null>(null);
6465
const [searchedAsset, setSearchedAsset] = useState("")
6566
const dataTableRef = useRef(null);
@@ -202,6 +203,16 @@ const DashboardAssets: React.FC<DashboardAssetsProps> = ({ setBlockerProp, setPr
202203
}
203204
}, [router.isReady, editOnboardAsset.successToast])
204205

206+
useEffect(() => {
207+
if (!selectedRow?.id) { setIsOnboarded(false); return; }
208+
axios.get(`${API_URL}/onboarding-asset/${selectedRow.id}`, {
209+
headers: { "Content-Type": "application/json", Accept: "application/json" },
210+
withCredentials: true,
211+
})
212+
.then(res => setIsOnboarded(!!(res.data && Object.keys(res.data).length)))
213+
.catch(() => setIsOnboarded(false));
214+
}, [selectedRow?.id])
215+
205216

206217
useEffect(() => {
207218
if (onboardAsset && showBlocker === false) {
@@ -302,7 +313,7 @@ const DashboardAssets: React.FC<DashboardAssetsProps> = ({ setBlockerProp, setPr
302313
</div>
303314
<div className="selected_product_actions">
304315
{selectedRow.id && (
305-
<IfricIdBadge ifricId={selectedRow.id} toast={toast} setShowBlocker={setShowBlocker} editOnboardBodyTemplate={editOnboardBodyTemplate}/>
316+
<IfricIdBadge ifricId={selectedRow.id} toast={toast} setShowBlocker={setShowBlocker} editOnboardBodyTemplate={editOnboardBodyTemplate} isOnboarded={isOnboarded}/>
306317
)}
307318
<div className="flex gap-3" style={{paddingRight: "70px", minWidth: "110px"}}>
308319
<div className="flex align-items-center gap-2">

frontend/src/components/dashboard/edit-onboard-form.tsx

Lines changed: 67 additions & 79 deletions
Large diffs are not rendered by default.

frontend/src/components/dashboard/ifric-id-badge.tsx

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ interface IfricIdBadgeProps{
1515
toast: RefObject<Toast>;
1616
setShowBlocker: React.Dispatch<React.SetStateAction<boolean>>;
1717
editOnboardBodyTemplate: () => void;
18+
isOnboarded: boolean;
1819
}
19-
export default function IfricIdBadge({ ifricId, toast, setShowBlocker, editOnboardBodyTemplate }: IfricIdBadgeProps) {
20+
export default function IfricIdBadge({ ifricId, toast, setShowBlocker, editOnboardBodyTemplate, isOnboarded }: IfricIdBadgeProps) {
2021

2122
const [showDropdown, setShowDropdown] = useState(false);
2223
const op = useRef<OverlayPanel>(null);
@@ -35,15 +36,45 @@ export default function IfricIdBadge({ ifricId, toast, setShowBlocker, editOnboa
3536
const menuModel = [
3637
{
3738
label: t("onboard"),
39+
disabled: isOnboarded,
40+
template: isOnboarded
41+
? (_item: any, options: any) => (
42+
<div
43+
className={options.className}
44+
style={{ pointerEvents: "none", opacity: 0.55, cursor: "not-allowed", flexDirection: "column", alignItems: "flex-start", gap: "2px" }}
45+
title="This asset is already onboarded"
46+
>
47+
<span className={options.labelClassName}>{t("onboard")}</span>
48+
<small style={{ fontSize: "11px", color: "#6b7280" }}>
49+
Already onboarded
50+
</small>
51+
</div>
52+
)
53+
: undefined,
3854
command: () => {
3955
setShowBlocker(true);
4056
},
4157
},
4258
{
4359
label: t("edit_onboard"),
60+
disabled: !isOnboarded,
61+
template: !isOnboarded
62+
? (_item: any, options: any) => (
63+
<div
64+
className={options.className}
65+
style={{ pointerEvents: "none", opacity: 0.55, cursor: "not-allowed", flexDirection: "column", alignItems: "flex-start", gap: "2px" }}
66+
title="Onboard this asset first"
67+
>
68+
<span className={options.labelClassName}>{t("edit_onboard")}</span>
69+
<small style={{ fontSize: "11px", color: "#6b7280" }}>
70+
Onboard first
71+
</small>
72+
</div>
73+
)
74+
: undefined,
4475
command: () => {
4576
editOnboardBodyTemplate();
46-
},
77+
},
4778
}
4879
];
4980

frontend/src/components/dashboard/onboard-form.tsx

Lines changed: 37 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { OnboardData } from "@/types/onboard-form";
3333
import { Asset } from "@/types/asset-types";
3434
import YAML from 'yaml';
3535
import { getAssetById, getRawAssetById } from "@/utility/asset";
36+
import SpecEditor, { OpcUaSpec, MqttSpec, SpecItem } from "./spec-editor";
3637

3738
type OnboardDataKey = keyof OnboardData;
3839

@@ -161,6 +162,8 @@ const OnboardForm: React.FC<OnboardFormProps> = ({
161162
}) => {
162163
const [assetData, setAssetData] = useState<Asset | null>(null);
163164
const [showSecondaryConfig, setShowSecondaryConfig] = useState(false);
165+
const [specItems, setSpecItems] = useState<SpecItem[]>([]);
166+
const [secondarySpecItems, setSecondarySpecItems] = useState<SpecItem[]>([]);
164167

165168

166169

@@ -183,18 +186,14 @@ const OnboardForm: React.FC<OnboardFormProps> = ({
183186
const mqttSpecs = extractMqttSpecs(rawAssetData);
184187

185188
if (protocol === "opc-ua") {
186-
// Primary: OPC-UA binding points (with placeholder if none)
187-
appConfig = buildOpcUaConfigTemplate(opcUaSpecs);
188-
// Secondary: any parameters using MQTT-style binding points
189+
setSpecItems(opcUaSpecs);
189190
if (mqttSpecs.length > 0) {
190-
secondaryAppConfig = buildMqttConfigTemplate(mqttSpecs);
191+
setSecondarySpecItems(mqttSpecs);
191192
}
192193
} else {
193-
// Primary: MQTT binding points (with placeholder if none)
194-
appConfig = buildMqttConfigTemplate(mqttSpecs);
195-
// Secondary: any parameters using OPC-UA-style binding points
194+
setSpecItems(mqttSpecs);
196195
if (opcUaSpecs.length > 0) {
197-
secondaryAppConfig = buildOpcUaConfigTemplate(opcUaSpecs);
196+
setSecondarySpecItems(opcUaSpecs);
198197
}
199198
}
200199
}
@@ -212,8 +211,6 @@ const OnboardForm: React.FC<OnboardFormProps> = ({
212211
pod_name: podName,
213212
device_id: assetDataFromScorio?.id,
214213
gateway_id: assetDataFromScorio?.id,
215-
app_config: appConfig,
216-
secondary_app_config: secondaryAppConfig
217214
}));
218215
} catch (error) {
219216
console.error("Failed to fetch asset data:", error);
@@ -343,10 +340,10 @@ const OnboardForm: React.FC<OnboardFormProps> = ({
343340
}
344341
break;
345342
case 1: // Configuration
346-
if (!onboardForm.app_config || !onboardForm.pod_name) {
343+
if (specItems.length === 0 || !onboardForm.pod_name) {
347344
setValidateInput(prev => ({
348345
...prev,
349-
app_config: !onboardForm.app_config
346+
app_config: specItems.length === 0
350347
}));
351348
isValid = false;
352349
}
@@ -402,7 +399,6 @@ const OnboardForm: React.FC<OnboardFormProps> = ({
402399
const {
403400
ip_address,
404401
protocol,
405-
app_config,
406402
pod_name,
407403
pdt_mqtt_hostname,
408404
pdt_mqtt_port,
@@ -425,7 +421,7 @@ const OnboardForm: React.FC<OnboardFormProps> = ({
425421

426422

427423
if (ip_address === undefined || ip_address === "" ||
428-
app_config === undefined || app_config === "" ||
424+
specItems.length === 0 ||
429425
protocol === undefined || protocol === "" ||
430426
pdt_mqtt_hostname === undefined || pdt_mqtt_hostname === "" ||
431427
pdt_mqtt_port === undefined || pdt_mqtt_port === 0 ||
@@ -439,27 +435,17 @@ const OnboardForm: React.FC<OnboardFormProps> = ({
439435
showToast('error', "Error", "Please fill all required fields")
440436
} else {
441437

442-
// Check if app_config is not empty and is valid YAML
443-
try {
444-
parsedConfig = YAML.parse(onboardForm.app_config);
445-
} catch (error) {
446-
console.error("Invalid YAML in app_config");
447-
showToast('error', 'Error', 'Invalid YAML in app_config');
448-
setValidateInput(validate => ({ ...validate, app_config: true }))
449-
}
438+
// Build config objects from spec editor items
439+
parsedConfig = onboardForm.protocol === "opc-ua"
440+
? { fusionopcuadataservice: { specification: specItems } }
441+
: { fusionmqttdataservice: { specification: specItems } };
450442

451443
let parsedSecondaryConfig: Record<string, any> | undefined = undefined;
452-
if (showSecondaryConfig && onboardForm.secondary_app_config) {
453-
try {
454-
parsedSecondaryConfig = YAML.parse(onboardForm.secondary_app_config);
455-
if (typeof parsedSecondaryConfig !== "object") {
456-
parsedSecondaryConfig = undefined;
457-
showToast('error', 'Error', 'Invalid YAML in secondary configuration');
458-
}
459-
} catch (error) {
460-
console.error("Invalid YAML in secondary_app_config");
461-
showToast('error', 'Error', 'Invalid YAML in secondary configuration');
462-
}
444+
if (showSecondaryConfig && secondarySpecItems.length > 0) {
445+
const secondaryProtocol = onboardForm.protocol === "opc-ua" ? "mqtt" : "opc-ua";
446+
parsedSecondaryConfig = secondaryProtocol === "opc-ua"
447+
? { fusionopcuadataservice: { specification: secondarySpecItems } }
448+
: { fusionmqttdataservice: { specification: secondarySpecItems } };
463449
}
464450

465451
if (typeof parsedConfig === "object") {
@@ -663,30 +649,17 @@ const OnboardForm: React.FC<OnboardFormProps> = ({
663649
{t("dashboard:app_config")} <span className="text-red-500">*</span>
664650
</label>
665651
<small className="block mb-2 text-gray-600">
666-
YAML configuration mapping machine data points (OPC-UA nodes or MQTT topics) to digital twin properties
652+
Define how machine data points ({onboardForm.protocol === "opc-ua" ? "OPC-UA nodes" : "MQTT topics"}) map to digital twin properties
667653
</small>
668-
<InputTextarea
669-
id="app_config"
670-
value={onboardForm.app_config}
671-
rows={12}
672-
cols={30}
673-
onChange={(e) => handleInputTextAreaChange(e, "app_config")}
674-
className={`w-full font-mono ${validateInput?.app_config ? 'p-invalid' : ''}`}
654+
<SpecEditor
655+
protocol={(onboardForm.protocol === "opc-ua" || onboardForm.protocol === "mqtt") ? onboardForm.protocol : "opc-ua"}
656+
items={specItems}
657+
onChange={setSpecItems}
658+
hasError={validateInput.app_config}
675659
/>
676660
{validateInput?.app_config && (
677-
<small className="p-error">Valid YAML configuration is required</small>
661+
<small className="p-error">At least one data mapping is required</small>
678662
)}
679-
<div className="mt-2">
680-
<Button
681-
type="button"
682-
label="Prettify YAML"
683-
icon="pi pi-sparkles"
684-
onClick={prettifyYAML}
685-
size="small"
686-
outlined
687-
className="prettify-btn"
688-
/>
689-
</div>
690663
</div>
691664

692665
{!showSecondaryConfig ? (
@@ -717,14 +690,16 @@ const OnboardForm: React.FC<OnboardFormProps> = ({
717690
icon="pi pi-times"
718691
onClick={() => {
719692
setShowSecondaryConfig(false);
720-
setOnboardForm(prev => ({ ...prev, secondary_app_config: "", secondary_ip_address: "", secondary_dataservice_image_config: "" }));
693+
setSecondarySpecItems([]);
694+
setOnboardForm(prev => ({ ...prev, secondary_ip_address: "", secondary_dataservice_image_config: "" }));
721695
}}
722696
size="small"
723697
text
724698
severity="danger"
725699
tooltip="Remove secondary configuration"
726700
/>
727701
</div>
702+
<hr />
728703
<div className="field mb-3">
729704
<label htmlFor="secondary_ip_address" className="font-semibold">
730705
Secondary Connection URL
@@ -757,24 +732,15 @@ const OnboardForm: React.FC<OnboardFormProps> = ({
757732
className="w-full"
758733
/>
759734
</div>
760-
<InputTextarea
761-
id="secondary_app_config"
762-
rows={8}
763-
cols={30}
764-
onChange={(e) => handleInputTextAreaChange(e, "secondary_app_config")}
765-
className="w-full font-mono"
735+
<label className="font-semibold block mb-1">Data Mappings</label>
736+
<small className="block mb-2 text-gray-600">
737+
Define {onboardForm.protocol === "opc-ua" ? "MQTT" : "OPC-UA"} data mappings for the secondary configuration
738+
</small>
739+
<SpecEditor
740+
protocol={onboardForm.protocol === "opc-ua" ? "mqtt" : "opc-ua"}
741+
items={secondarySpecItems}
742+
onChange={setSecondarySpecItems}
766743
/>
767-
<div className="mt-2">
768-
<Button
769-
type="button"
770-
label="Prettify YAML"
771-
icon="pi pi-sparkles"
772-
onClick={prettifySecondaryYAML}
773-
size="small"
774-
outlined
775-
className="prettify-btn"
776-
/>
777-
</div>
778744
</div>
779745
)}
780746
</div>

0 commit comments

Comments
 (0)