diff --git a/openc3-cosmos-cmd-tlm-api/app/models/streaming_thread.rb b/openc3-cosmos-cmd-tlm-api/app/models/streaming_thread.rb index 610708ae09..db979fe818 100644 --- a/openc3-cosmos-cmd-tlm-api/app/models/streaming_thread.rb +++ b/openc3-cosmos-cmd-tlm-api/app/models/streaming_thread.rb @@ -159,8 +159,17 @@ def handle_message(msg_hash, objects) if first_object.stream_mode == :RAW return handle_raw_packet(msg_hash['buffer'], objects, time) else # @stream_mode == :DECOM or :REDUCED_X + # Parse json_data to extract extra field containing username and other metadata + json_data = msg_hash["json_data"] + extra = nil + if json_data + parsed = JSON.parse(json_data, allow_nan: true, create_additions: true) + if parsed['extra'] + extra = JSON.parse(parsed['extra'], allow_nan: true, create_additions: true) + end + end json_packet = OpenC3::JsonPacket.new(first_object.cmd_or_tlm, first_object.target_name, first_object.packet_name, - time, OpenC3::ConfigParser.handle_true_false(msg_hash["stored"]), msg_hash["json_data"]) + time, OpenC3::ConfigParser.handle_true_false(msg_hash["stored"]), json_data, extra: extra) return handle_json_packet(json_packet, objects) end end @@ -171,6 +180,7 @@ def handle_json_packet(json_packet, objects) return nil if objects.length <= 0 result = {} result['__time'] = time.to_nsec_from_epoch + result['__extra'] = json_packet.extra if json_packet.extra objects.each do |object| # OpenC3::Logger.debug("item:#{object.item_name} key:#{object.key} type:#{object.value_type}") if object.item_name diff --git a/openc3-cosmos-init/plugins/packages/openc3-cosmos-tool-dataextractor/src/tools/DataExtractor/DataExtractor.vue b/openc3-cosmos-init/plugins/packages/openc3-cosmos-tool-dataextractor/src/tools/DataExtractor/DataExtractor.vue index 057be04644..adb91442f9 100644 --- a/openc3-cosmos-init/plugins/packages/openc3-cosmos-tool-dataextractor/src/tools/DataExtractor/DataExtractor.vue +++ b/openc3-cosmos-init/plugins/packages/openc3-cosmos-tool-dataextractor/src/tools/DataExtractor/DataExtractor.vue @@ -315,7 +315,7 @@ // Putting large data into Vue data section causes lots of overhead let dataExtractorRawData = [] -import { Cable, OpenC3Api } from '@openc3/js-common/services' +import { Api, Cable, OpenC3Api } from '@openc3/js-common/services' import { Config, OpenConfigDialog, @@ -391,9 +391,13 @@ export default { // uniqueIgnoreOptions: ['NO', 'YES'], cable: new Cable(), subscription: null, + enterprise: false, } }, computed: { + hasCommands: function () { + return this.items.some((item) => item.cmdOrTlm === 'CMD') + }, menus: function () { return [ { @@ -566,6 +570,11 @@ export default { .catch((error) => { // Do nothing }) + await Api.get('/openc3-api/info').then((response) => { + if (response.data.enterprise) { + this.enterprise = true + } + }) let now = new Date() this.todaysDate = this.formatDate(now, this.timeZone) this.startDate = this.formatDate(now - 3600000, this.timeZone) // last hr data @@ -846,11 +855,16 @@ export default { for (let packet of data) { let packetKeys = Object.keys(packet) packetKeys.forEach(keys.add, keys) - this.itemsReceived += packetKeys.length - 2 // Don't count __type and __time - this.totalItemsReceived += packetKeys.length - 2 + // Don't count metadata keys (__type, __time, __extra) + let metadataCount = packetKeys.filter((k) => + k.startsWith('__'), + ).length + this.itemsReceived += packetKeys.length - metadataCount + this.totalItemsReceived += packetKeys.length - metadataCount } keys.delete('__type') keys.delete('__time') + keys.delete('__extra') this.buildHeaders([...keys]) dataExtractorRawData.push(data) this.progress = Math.ceil( @@ -874,6 +888,14 @@ export default { this.columnHeaders.push('TIME') this.columnHeaders.push('TARGET') this.columnHeaders.push('PACKET') + // Only add USERNAME and APPROVER columns for command data + if (this.hasCommands) { + this.columnHeaders.push('USERNAME') + // APPROVER is only available in Enterprise + if (this.enterprise) { + this.columnHeaders.push('APPROVER') + } + } } itemKeys.forEach((item) => { if (item.slice(0, 2) === '__') return @@ -1001,6 +1023,13 @@ export default { row[0] = new Date(packet['__time'] / 1000000).toISOString() row[1] = targetName row[2] = packetName + // Add username and approver columns for command data + if (this.hasCommands) { + row[3] = packet['__extra']?.username || '' + if (this.enterprise) { + row[4] = packet['__extra']?.approver || '' + } + } } outputFile.push(row.join(this.delimiter)) } diff --git a/openc3/lib/openc3/microservices/interface_microservice.rb b/openc3/lib/openc3/microservices/interface_microservice.rb index dd8927d157..46e50743b6 100644 --- a/openc3/lib/openc3/microservices/interface_microservice.rb +++ b/openc3/lib/openc3/microservices/interface_microservice.rb @@ -89,6 +89,7 @@ def run InterfaceTopic.receive_commands(@interface, scope: @scope) do |topic, msg_id, msg_hash, _redis| OpenC3.with_context(msg_hash) do release_critical = false + critical_model = nil msgid_seconds_from_epoch = msg_id.split('-')[0].to_i / 1000.0 delta = Time.now.to_f - msgid_seconds_from_epoch @metric.set(name: 'interface_topic_delta_seconds', value: delta, type: 'gauge', unit: 'seconds', help: 'Delta time between data written to stream and interface cmd start') if @metric @@ -188,9 +189,9 @@ def run end if msg_hash.key?('release_critical') # Note: intentional fall through below this point - model = CriticalCmdModel.get_model(name: msg_hash['release_critical'], scope: @scope) - if model - msg_hash = model.cmd_hash + critical_model = CriticalCmdModel.get_model(name: msg_hash['release_critical'], scope: @scope) + if critical_model + msg_hash = critical_model.cmd_hash release_critical = true else next "Critical command #{msg_hash['release_critical']} not found" @@ -279,6 +280,10 @@ def run command.extra ||= {} command.extra['cmd_string'] = msg_hash['cmd_string'] command.extra['username'] = msg_hash['username'] + # Add approver info if this was a critical command that was approved + if critical_model + command.extra['approver'] = critical_model.approver + end hazardous, hazardous_description = System.commands.cmd_pkt_hazardous?(command) # Initial Are you sure? Hazardous check diff --git a/openc3/python/openc3/microservices/interface_microservice.py b/openc3/python/openc3/microservices/interface_microservice.py index db03e6e5cf..491ba1bd50 100644 --- a/openc3/python/openc3/microservices/interface_microservice.py +++ b/openc3/python/openc3/microservices/interface_microservice.py @@ -101,6 +101,7 @@ def run(self): def process_cmd(self, topic, msg_id, msg_hash, _redis): # OpenC3.with_context(msg_hash) do release_critical = False + critical_model = None if msg_hash.get(b"shutdown"): return "Shutdown" @@ -218,13 +219,13 @@ def process_cmd(self, topic, msg_id, msg_hash, _redis): handle_inject_tlm(msg_hash[b"inject_tlm"], self.scope) return "SUCCESS" if msg_hash.get(b"release_critical"): - model = CriticalCmdModel.get_model(name=msg_hash[b"release_critical"].decode(), scope=self.scope) - if model is not None: - msg_hash = model.cmd_hash + # Note: intentional fall through below this point + critical_model = CriticalCmdModel.get_model(name=msg_hash[b"release_critical"].decode(), scope=self.scope) + if critical_model is not None: + msg_hash = critical_model.cmd_hash release_critical = True else: return f"Critical command {msg_hash[b'release_critical'].decode()} not found" - return "SUCCESS" if msg_hash.get(b"target_control"): try: params = json.loads(msg_hash[b"target_control"]) @@ -307,6 +308,9 @@ def process_cmd(self, topic, msg_id, msg_hash, _redis): command.extra = command.extra or {} command.extra["cmd_string"] = msg_hash[b"cmd_string"].decode() command.extra["username"] = msg_hash[b"username"].decode() + # Add approver info if this was a critical command that was approved + if critical_model is not None: + command.extra["approver"] = critical_model.approver hazardous, hazardous_description = System.commands.cmd_pkt_hazardous(command) if hazardous_check: