Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
aeed15a
P6C: expose dynamic DataSet and authored report APIs
masarray Sep 21, 2026
3dc4e9b
P6C: add authoring helpers and runtime ownership state
masarray Sep 21, 2026
5b082b3
P6C: normalize static and authored report UI snapshots
masarray Sep 21, 2026
05bfe2f
P6C: own dynamic DataSet runtime on report association
masarray Sep 21, 2026
ce6f545
P6C: classify static versus runtime-owned DataSets
masarray Sep 21, 2026
c5831d6
P6C: create and delete verified owned dynamic DataSets
masarray Sep 21, 2026
c9877db
P6C: apply authored DataSet TrgOps OptFlds through proven runtimes
masarray Sep 21, 2026
6ccfc1c
P6C: unify static and authored report lifecycle
masarray Sep 21, 2026
5a25878
P6C: harden Qt string and index typing in authoring path
masarray Sep 21, 2026
d547507
P6C: add ordered dynamic DataSet authoring dialog
masarray Sep 21, 2026
ff34946
P6C: gate authored RCB fields by discovered attributes
masarray Sep 21, 2026
5b29793
P6C: add advanced RCB authoring dialog
masarray Sep 21, 2026
b8d6dec
P6C: package DataSet and report authoring dialogs
masarray Sep 21, 2026
cd677d4
P6C: wire dynamic DataSet and report authoring commands
masarray Sep 21, 2026
83e722b
P6C: surface static immutability and dynamic ownership
masarray Sep 21, 2026
df33622
P6C: retain report association state during authoring operations
masarray Sep 21, 2026
be50159
P6C: add DataSet and advanced report authoring regression contract
masarray Sep 21, 2026
66e73ea
P6C: gate dynamic DataSet and report authoring in CI
masarray Sep 21, 2026
14e746e
P6C: prove static authored report path and fail-closed dynamic actions
masarray Sep 21, 2026
4103e23
P6C: remove accidental duplicate report controller tail
masarray Sep 21, 2026
faebab7
P6C: support explicit canonical RCB binding restore
masarray Sep 21, 2026
02b7267
P6C: expose safe restore to canonical report binding
masarray Sep 21, 2026
6230216
P6C: gate legacy static enable while dynamic binding is active
masarray Sep 21, 2026
afdda0e
P6C: prevent legacy report enable on authored dynamic binding
masarray Sep 21, 2026
81ea396
P6C: restore complete DataSet helper and ownership projection
masarray Sep 23, 2026
90e2a73
P6C: regression gate for complete authoring helper and single controller
masarray Sep 23, 2026
9dae81a
P6C: assert actual owned-only delete dialog copy
masarray Sep 23, 2026
b69b813
P6C: reconstruct complete regression script from original tested source
masarray Sep 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/ied-simulator-qt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,16 @@ jobs:
apps/ied_simulator/qml/Main.qml
apps/ied_simulator/CMakeLists.txt

- name: P6C Dynamic DataSet + Report Authoring contract
run: >-
python3 apps/ied_simulator/test_browser_report_authoring.py
apps/ied_simulator/src/MmsReportController.cpp
apps/ied_simulator/qml/IedBrowserWorkspace.qml
apps/ied_simulator/qml/BrowserDataSetAuthoringDialog.qml
apps/ied_simulator/qml/BrowserReportAuthoringDialog.qml
apps/ied_simulator/qml/BrowserDataSetPane.qml
apps/ied_simulator/src/MmsReportController.hpp

- name: IED Browser session ownership gates
env:
QT_QPA_PLATFORM: offscreen
Expand Down
2 changes: 2 additions & 0 deletions apps/ied_simulator/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ qt_add_qml_module(arstack_ied_simulator
qml/BrowserGoosePane.qml
qml/BrowserGlobalDataPane.qml
qml/BrowserControlDialog.qml
qml/BrowserDataSetAuthoringDialog.qml
qml/BrowserReportAuthoringDialog.qml
qml/SnifferWorkspace.qml
qml/MmsClientWorkspace.qml
qml/ReportsWorkspace.qml
Expand Down
343 changes: 343 additions & 0 deletions apps/ied_simulator/qml/BrowserDataSetAuthoringDialog.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,343 @@
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

Dialog {
id: root

required property var theme
required property var reports

property string pendingDeleteReference: ""
readonly property int maximumMembers: 64

title: "Dynamic DataSet Authoring"
modal: true
width: 700
height: 620
anchors.centerIn: Overlay.overlay
standardButtons: Dialog.Close

ListModel { id: memberModel }

function findMember(domain, item) {
for (var i = 0; i < memberModel.count; ++i) {
var row = memberModel.get(i)
if (row.mmsDomain === domain && row.mmsItem === item)
return i
}
return -1
}

function resetDraft() {
memberModel.clear()
referenceField.text = ""
statusLabel.text = "Draft is local until Create is explicitly confirmed."
}

function newDraft() {
resetDraft()
root.open()
}

function addSelected(node) {
if (!node || node.kind !== "DA" || node.readable !== true
|| !node.mmsDomain || !node.mmsItem || !node.reference) {
statusLabel.text = "Select one readable canonical DataAttribute first."
root.open()
return false
}
if (findMember(node.mmsDomain, node.mmsItem) >= 0) {
statusLabel.text = "That MMS member is already in the draft."
root.open()
return true
}
if (memberModel.count >= root.maximumMembers) {
statusLabel.text = "Draft is full (64 members)."
root.open()
return false
}
memberModel.append({
"reference": node.reference,
"mmsDomain": node.mmsDomain,
"mmsItem": node.mmsItem,
"functionalConstraint": node.functionalConstraint || ""
})
if (referenceField.text.length === 0)
referenceField.text = node.mmsDomain + "/LLN0.ARStackDynamic1"
statusLabel.text = "Added canonical member " + node.reference
root.open()
return true
}

function memberPayload() {
var result = []
for (var i = 0; i < memberModel.count; ++i) {
var row = memberModel.get(i)
result.push({
"reference": row.reference,
"mmsDomain": row.mmsDomain,
"mmsItem": row.mmsItem,
"functionalConstraint": row.functionalConstraint
})
}
return result
}

function createConfirmed() {
var accepted = reports.createDynamicDataSet(
referenceField.text, memberPayload())
if (accepted) {
statusLabel.text = "DefineNamedVariableList started; verification is mandatory."
root.close()
} else {
statusLabel.text = reports.lastError.length
? reports.lastError : "Dynamic DataSet create was not started."
}
}

function requestDelete(dataSet) {
if (!dataSet || dataSet.dynamicOwned !== true) {
pendingDeleteReference = ""
deleteError.text =
"Only DataSets created by this live report association can be deleted here."
deleteDialog.open()
return false
}
pendingDeleteReference = dataSet.reference || ""
deleteError.text = ""
deleteDialog.open()
return true
}

function deleteConfirmed() {
if (pendingDeleteReference.length === 0)
return
reports.deleteDynamicDataSet(pendingDeleteReference)
pendingDeleteReference = ""
}

contentItem: ColumnLayout {
spacing: 8

Rectangle {
Layout.fillWidth: true
implicitHeight: 74
radius: 6
color: theme.surface
border.width: 1
border.color: theme.lineSoft

ColumnLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 3
Label {
text: "DATASET REFERENCE"
color: theme.muted
font.pixelSize: 7
font.weight: Font.Bold
}
TextField {
id: referenceField
Layout.fillWidth: true
placeholderText: "LogicalDevice/LLN0.ARStackDynamic1"
selectByMouse: true
enabled: !reports.busy
}
}
}

RowLayout {
Layout.fillWidth: true
Label {
Layout.fillWidth: true
text: "Ordered members · " + memberModel.count
+ " / " + root.maximumMembers
color: theme.text
font.pixelSize: 10
font.weight: Font.DemiBold
}
Button {
text: "Clear draft"
enabled: memberModel.count > 0 && !reports.busy
onClicked: memberModel.clear()
}
}

ListView {
id: memberList
Layout.fillWidth: true
Layout.fillHeight: true
model: memberModel
clip: true
reuseItems: true
spacing: 1
ScrollBar.vertical: ScrollBar {}

delegate: Rectangle {
required property int index
required property string reference
required property string mmsDomain
required property string mmsItem
required property string functionalConstraint

width: memberList.width
height: 54
color: index % 2 ? theme.surfaceSoft : theme.surface
border.width: 1
border.color: theme.lineSoft

RowLayout {
anchors.fill: parent
anchors.leftMargin: 9
anchors.rightMargin: 8
spacing: 7

Label {
text: String(index + 1)
color: theme.muted
font.pixelSize: 8
Layout.preferredWidth: 24
horizontalAlignment: Text.AlignHCenter
}

ColumnLayout {
Layout.fillWidth: true
spacing: 1
Label {
Layout.fillWidth: true
text: reference
color: theme.text
font.pixelSize: 9
font.weight: Font.DemiBold
elide: Text.ElideMiddle
}
Label {
Layout.fillWidth: true
text: mmsDomain + "/" + mmsItem
+ (functionalConstraint.length
? " · FC=" + functionalConstraint : "")
color: theme.muted
font.pixelSize: 7
elide: Text.ElideMiddle
}
}

Button {
text: "↑"
enabled: index > 0 && !reports.busy
onClicked: memberModel.move(index, index - 1, 1)
}
Button {
text: "↓"
enabled: index + 1 < memberModel.count && !reports.busy
onClicked: memberModel.move(index, index + 1, 1)
}
Button {
text: "Remove"
enabled: !reports.busy
onClicked: memberModel.remove(index)
}
}
}

Label {
anchors.centerIn: parent
visible: memberModel.count === 0
width: Math.max(220, parent.width - 60)
text: "Add readable DataAttributes from Data Model. Member order is preserved exactly in DefineNamedVariableList."
color: theme.muted
font.pixelSize: 9
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
}

Label {
id: statusLabel
Layout.fillWidth: true
text: "Draft is local until Create is explicitly confirmed."
color: reports.lastError.length ? theme.red : theme.muted
font.pixelSize: 8
wrapMode: Text.WordWrap
}

RowLayout {
Layout.fillWidth: true
Label {
Layout.fillWidth: true
text: "Create uses the existing MMS association and verifies the resulting directory before ownership is recorded."
color: theme.muted
font.pixelSize: 8
wrapMode: Text.WordWrap
}
Button {
text: reports.busy ? "Working…" : "Create…"
enabled: reports.connected && !reports.busy
&& referenceField.text.trim().length > 0
&& memberModel.count > 0
onClicked: createConfirm.open()
}
}
}

Dialog {
id: createConfirm
title: "Confirm dynamic DataSet create"
modal: true
anchors.centerIn: Overlay.overlay
standardButtons: Dialog.Ok | Dialog.Cancel
onAccepted: root.createConfirmed()
contentItem: Label {
width: 470
text: "Create " + referenceField.text
+ " with " + memberModel.count
+ " ordered member(s)? This sends one DefineNamedVariableList request and requires directory read-back verification."
color: theme.text
font.pixelSize: 9
wrapMode: Text.WordWrap
}
}

Dialog {
id: deleteDialog
title: "Confirm dynamic DataSet delete"
modal: true
anchors.centerIn: Overlay.overlay
standardButtons: root.pendingDeleteReference.length > 0
? Dialog.Ok | Dialog.Cancel : Dialog.Close
onAccepted: root.deleteConfirmed()
contentItem: ColumnLayout {
width: 470
spacing: 6
Label {
Layout.fillWidth: true
text: root.pendingDeleteReference.length > 0
? "Delete owned dynamic DataSet " + root.pendingDeleteReference + "?"
: "Delete refused."
color: theme.text
font.pixelSize: 9
font.weight: Font.DemiBold
wrapMode: Text.WordWrap
}
Label {
id: deleteError
Layout.fillWidth: true
text: ""
color: deleteError.text.length ? theme.red : theme.muted
font.pixelSize: 8
wrapMode: Text.WordWrap
}
Label {
visible: root.pendingDeleteReference.length > 0
Layout.fillWidth: true
text: "ARStack uses owned-only DeleteNamedVariableList. Static and foreign dynamic DataSets cannot be deleted from this workflow."
color: theme.muted
font.pixelSize: 8
wrapMode: Text.WordWrap
}
}
}
}
Loading
Loading