openc3 6.3.0 → 6.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/openc3cli +1 -11
- data/data/config/interface_modifiers.yaml +1 -1
- data/data/config/microservice.yaml +1 -1
- data/data/config/plugins.yaml +1 -1
- data/data/config/tool.yaml +1 -1
- data/ext/openc3/ext/burst_protocol/burst_protocol.c +5 -1
- data/lib/openc3/api/api.rb +7 -1
- data/lib/openc3/interfaces/file_interface.rb +36 -7
- data/lib/openc3/interfaces/protocols/burst_protocol.rb +3 -3
- data/lib/openc3/interfaces/protocols/preidentified_protocol.rb +20 -26
- data/lib/openc3/interfaces.rb +4 -3
- data/lib/openc3/logs/log_writer.rb +11 -8
- data/lib/openc3/logs/packet_log_reader.rb +2 -2
- data/lib/openc3/logs/packet_log_writer.rb +1 -1
- data/lib/openc3/microservices/interface_microservice.rb +1 -1
- data/lib/openc3/microservices/microservice.rb +2 -1
- data/lib/openc3/models/model.rb +6 -2
- data/lib/openc3/models/script_status_model.rb +242 -0
- data/lib/openc3/script/api_shared.rb +4 -0
- data/lib/openc3/script/script_runner.rb +22 -7
- data/lib/openc3/utilities/authentication.rb +1 -1
- data/lib/openc3/utilities/cosmos_rails_formatter.rb +1 -1
- data/lib/openc3/utilities/message_log.rb +2 -0
- data/lib/openc3/utilities/metric.rb +1 -1
- data/lib/openc3/version.rb +5 -5
- data/templates/tool_angular/package.json +2 -2
- data/templates/tool_react/package.json +1 -1
- data/templates/tool_svelte/package.json +1 -1
- data/templates/tool_vue/eslint.config.mjs +17 -41
- data/templates/tool_vue/package.json +3 -3
- data/templates/widget/package.json +2 -2
- metadata +5 -4
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: c408df49ec8cf1fbcf88e8c05207ac1c65274d56dbee94986c4f56308bc52b23
         | 
| 4 | 
            +
              data.tar.gz: 8e8897d618e69898470fcd8a201ba968688fbe598c4ecb46d1a375da14282490
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: c37180a0814a4ceb79b6503d52a83e10cba964351f07841db692e44bdb5a619f95b7a955283982f73218dc5f6670bef113654b917fc92ed99a9c71ff8e000e36
         | 
| 7 | 
            +
              data.tar.gz: fa11395f5c24e207e6e6eb6f74b2b8f53c882222572f39cf7d6a6e95668dd99c5ad24015e832df937fb5f12800c92e3c7f55319716a46fc6a07cdbb99abc2e52
         | 
    
        data/bin/openc3cli
    CHANGED
    
    | @@ -592,28 +592,18 @@ def cli_script_monitor(script_id) | |
| 592 592 | 
             
                while (resp = api.read) do
         | 
| 593 593 | 
             
                  # see ScriptRunner.vue for types and states
         | 
| 594 594 | 
             
                  case resp['type']
         | 
| 595 | 
            -
                  when 'error', 'fatal'
         | 
| 596 | 
            -
                    $script_interrupt_text = ''
         | 
| 597 | 
            -
                    puts 'script failed'
         | 
| 598 | 
            -
                    break
         | 
| 599 595 | 
             
                  when 'file'
         | 
| 600 596 | 
             
                    puts "Filename #{resp['filename']} scope #{resp['scope']}"
         | 
| 601 597 | 
             
                  when 'line'
         | 
| 602 598 | 
             
                    fn = resp['filename'].nil? ? '<no file>' : resp['filename']
         | 
| 603 599 | 
             
                    puts "At [#{fn}:#{resp['line_no']}] state [#{resp['state']}]"
         | 
| 604 | 
            -
                    if resp['state'] == 'error'
         | 
| 600 | 
            +
                    if resp['state'] == 'error' or resp['state'] == 'crashed'
         | 
| 605 601 | 
             
                      $script_interrupt_text = ''
         | 
| 606 602 | 
             
                      puts 'script failed'
         | 
| 607 603 | 
             
                      break
         | 
| 608 604 | 
             
                    end
         | 
| 609 605 | 
             
                  when 'output'
         | 
| 610 606 | 
             
                    puts resp['line']
         | 
| 611 | 
            -
                  when 'paused'
         | 
| 612 | 
            -
                    if resp['state'] == 'fatal'
         | 
| 613 | 
            -
                      $script_interrupt_text = ''
         | 
| 614 | 
            -
                      puts 'script failed'
         | 
| 615 | 
            -
                      break
         | 
| 616 | 
            -
                    end
         | 
| 617 607 | 
             
                  when 'complete'
         | 
| 618 608 | 
             
                    $script_interrupt_text = ''
         | 
| 619 609 | 
             
                    puts 'script complete'
         | 
| @@ -240,7 +240,7 @@ CMD: | |
| 240 240 | 
             
              python_example: CMD python interface_microservice.py DEFAULT__INTERFACE__INT1
         | 
| 241 241 | 
             
            CONTAINER:
         | 
| 242 242 | 
             
              summary: Docker Container
         | 
| 243 | 
            -
              description: Container to execute and run the microservice in. Only used in COSMOS Enterprise | 
| 243 | 
            +
              description: Container to execute and run the microservice in. Only used in COSMOS Enterprise.
         | 
| 244 244 | 
             
              since: 5.7.0
         | 
| 245 245 | 
             
              parameters:
         | 
| 246 246 | 
             
                - name: Args
         | 
| @@ -107,7 +107,7 @@ MICROSERVICE: | |
| 107 107 | 
             
                      values: .+
         | 
| 108 108 | 
             
                CONTAINER:
         | 
| 109 109 | 
             
                  summary: Docker Container
         | 
| 110 | 
            -
                  description: Container to execute and run the microservice in. Only used in COSMOS Enterprise | 
| 110 | 
            +
                  description: Container to execute and run the microservice in. Only used in COSMOS Enterprise.
         | 
| 111 111 | 
             
                  parameters:
         | 
| 112 112 | 
             
                    - name: Args
         | 
| 113 113 | 
             
                      required: false
         | 
    
        data/data/config/plugins.yaml
    CHANGED
    
    | @@ -16,7 +16,7 @@ VARIABLE: | |
| 16 16 | 
             
            NEEDS_DEPENDENCIES:
         | 
| 17 17 | 
             
              summary: Indicates the plugin needs dependencies and sets the GEM_HOME environment variable
         | 
| 18 18 | 
             
              description: If the plugin has a top level lib folder or lists runtime dependencies in the gemspec,
         | 
| 19 | 
            -
                NEEDS_DEPENDENCIES is effectively already set. Note that in Enterprise | 
| 19 | 
            +
                NEEDS_DEPENDENCIES is effectively already set. Note that in Enterprise, having
         | 
| 20 20 | 
             
                NEEDS_DEPENDENCIES adds the NFS volume mount to the Kubernetes pod.
         | 
| 21 21 | 
             
              since: 5.5.0
         | 
| 22 22 | 
             
            INTERFACE:
         | 
    
        data/data/config/tool.yaml
    CHANGED
    
    | @@ -66,7 +66,7 @@ TOOL: | |
| 66 66 | 
             
                  description:
         | 
| 67 67 | 
             
                    Position of the tool starting at 2 (1 is reserved for Admin Console).
         | 
| 68 68 | 
             
                    Tools without a position are appended to the end as they are installed.
         | 
| 69 | 
            -
                    All COSMOS  | 
| 69 | 
            +
                    All COSMOS Core tools have consecutive integer values for position.
         | 
| 70 70 | 
             
                  since: 5.0.8
         | 
| 71 71 | 
             
                  parameters:
         | 
| 72 72 | 
             
                    - name: Position
         | 
| @@ -81,7 +81,11 @@ static VALUE burst_protocol_read_data(int argc, VALUE *argv, VALUE self) | |
| 81 81 | 
             
              };
         | 
| 82 82 |  | 
| 83 83 | 
             
              rb_str_concat(rb_ivar_get(self, id_ivar_data), data);
         | 
| 84 | 
            -
             | 
| 84 | 
            +
             | 
| 85 | 
            +
              /* Maintain extra from last read read_data */
         | 
| 86 | 
            +
              if (!((RSTRING_LEN(data) == 0) && (!(RTEST(extra))))) {
         | 
| 87 | 
            +
                rb_ivar_set(self, id_ivar_extra, extra);
         | 
| 88 | 
            +
              }
         | 
| 85 89 |  | 
| 86 90 | 
             
              while (1)
         | 
| 87 91 | 
             
              {
         | 
    
        data/lib/openc3/api/api.rb
    CHANGED
    
    | @@ -14,7 +14,7 @@ | |
| 14 14 | 
             
            # GNU Affero General Public License for more details.
         | 
| 15 15 |  | 
| 16 16 | 
             
            # Modified by OpenC3, Inc.
         | 
| 17 | 
            -
            # All changes Copyright  | 
| 17 | 
            +
            # All changes Copyright 2025, OpenC3, Inc.
         | 
| 18 18 | 
             
            # All Rights Reserved
         | 
| 19 19 | 
             
            #
         | 
| 20 20 | 
             
            # This file may also be used under the terms of a commercial license
         | 
| @@ -35,6 +35,12 @@ require 'openc3/api/target_api' | |
| 35 35 | 
             
            require 'openc3/api/tlm_api'
         | 
| 36 36 | 
             
            require 'openc3/utilities/authorization'
         | 
| 37 37 | 
             
            require 'openc3/topics/topic'
         | 
| 38 | 
            +
            begin
         | 
| 39 | 
            +
              require 'openc3-enterprise/api/cmd_authority_api'
         | 
| 40 | 
            +
            rescue LoadError
         | 
| 41 | 
            +
              # LoadError expected for Open Source Edition
         | 
| 42 | 
            +
            end
         | 
| 43 | 
            +
             | 
| 38 44 |  | 
| 39 45 | 
             
            module OpenC3
         | 
| 40 46 | 
             
              module Api
         | 
| @@ -18,12 +18,16 @@ | |
| 18 18 |  | 
| 19 19 | 
             
            require 'openc3/interfaces/interface'
         | 
| 20 20 | 
             
            require 'openc3/config/config_parser'
         | 
| 21 | 
            +
            require 'openc3/utilities/logger'
         | 
| 21 22 | 
             
            require 'thread'
         | 
| 22 23 | 
             
            require 'listen'
         | 
| 23 24 | 
             
            require 'fileutils'
         | 
| 25 | 
            +
            require 'zlib'
         | 
| 24 26 |  | 
| 25 27 | 
             
            module OpenC3
         | 
| 26 28 | 
             
              class FileInterface < Interface
         | 
| 29 | 
            +
                attr_reader :filename
         | 
| 30 | 
            +
             | 
| 27 31 | 
             
                # @param command_write_folder [String] Folder to write command files to - Set to nil to disallow writes
         | 
| 28 32 | 
             
                # @param telemetry_read_folder [String] Folder to read telemetry files from - Set to nil to disallow reads
         | 
| 29 33 | 
             
                # @param telemetry_archive_folder [String] Folder to move read telemetry files to - Set to DELETE to delete files
         | 
| @@ -62,6 +66,7 @@ module OpenC3 | |
| 62 66 | 
             
                  @write_raw_allowed = false unless @command_write_folder
         | 
| 63 67 |  | 
| 64 68 | 
             
                  @file = nil
         | 
| 69 | 
            +
                  @filename = ''
         | 
| 65 70 | 
             
                  @listener = nil
         | 
| 66 71 | 
             
                  @connected = false
         | 
| 67 72 | 
             
                  @extension = ".bin"
         | 
| @@ -69,6 +74,9 @@ module OpenC3 | |
| 69 74 | 
             
                  @queue = Queue.new
         | 
| 70 75 | 
             
                  @polling = false
         | 
| 71 76 | 
             
                  @recursive = false
         | 
| 77 | 
            +
                  @throttle = nil
         | 
| 78 | 
            +
                  @discard_file_header_bytes = nil
         | 
| 79 | 
            +
                  @sleeper = nil
         | 
| 72 80 | 
             
                end
         | 
| 73 81 |  | 
| 74 82 | 
             
                def connect
         | 
| @@ -90,6 +98,7 @@ module OpenC3 | |
| 90 98 | 
             
                def disconnect
         | 
| 91 99 | 
             
                  @file.close if @file and not @file.closed?
         | 
| 92 100 | 
             
                  @file = nil
         | 
| 101 | 
            +
                  @sleeper.cancel if @sleeper
         | 
| 93 102 | 
             
                  @listener.stop if @listener
         | 
| 94 103 | 
             
                  @listener = nil
         | 
| 95 104 | 
             
                  @queue << nil
         | 
| @@ -102,6 +111,10 @@ module OpenC3 | |
| 102 111 | 
             
                    if @file
         | 
| 103 112 | 
             
                      # Read more data from existing file
         | 
| 104 113 | 
             
                      data = @file.read(@file_read_size)
         | 
| 114 | 
            +
                      # Throttle after each read size
         | 
| 115 | 
            +
                      if @throttle and @sleeper.sleep(@throttle)
         | 
| 116 | 
            +
                        return nil, nil
         | 
| 117 | 
            +
                      end
         | 
| 105 118 | 
             
                      if data and data.length > 0
         | 
| 106 119 | 
             
                        read_interface_base(data, nil)
         | 
| 107 120 | 
             
                        return data, nil
         | 
| @@ -111,9 +124,16 @@ module OpenC3 | |
| 111 124 | 
             
                    end
         | 
| 112 125 |  | 
| 113 126 | 
             
                    # Find the next file to read
         | 
| 114 | 
            -
                     | 
| 115 | 
            -
                    if  | 
| 116 | 
            -
                       | 
| 127 | 
            +
                    @filename = get_next_telemetry_file()
         | 
| 128 | 
            +
                    if @filename
         | 
| 129 | 
            +
                      if File.extname(@filename) == ".gz"
         | 
| 130 | 
            +
                        @file = Zlib::GzipReader.open(@filename)
         | 
| 131 | 
            +
                      else
         | 
| 132 | 
            +
                        @file = File.open(@filename, "rb")
         | 
| 133 | 
            +
                      end
         | 
| 134 | 
            +
                      if @discard_file_header_bytes
         | 
| 135 | 
            +
                        @file.read(@discard_file_header_bytes)
         | 
| 136 | 
            +
                      end
         | 
| 117 137 | 
             
                      next
         | 
| 118 138 | 
             
                    end
         | 
| 119 139 |  | 
| @@ -135,8 +155,8 @@ module OpenC3 | |
| 135 155 |  | 
| 136 156 | 
             
                def convert_data_to_packet(data, extra = nil)
         | 
| 137 157 | 
             
                  packet = super(data, extra)
         | 
| 138 | 
            -
                  if packet | 
| 139 | 
            -
                    packet.stored =  | 
| 158 | 
            +
                  if packet
         | 
| 159 | 
            +
                    packet.stored = @stored
         | 
| 140 160 | 
             
                  end
         | 
| 141 161 | 
             
                  return packet
         | 
| 142 162 | 
             
                end
         | 
| @@ -156,6 +176,11 @@ module OpenC3 | |
| 156 176 | 
             
                    @polling = ConfigParser.handle_true_false(option_values[0])
         | 
| 157 177 | 
             
                  when 'RECURSIVE'
         | 
| 158 178 | 
             
                    @recursive = ConfigParser.handle_true_false(option_values[0])
         | 
| 179 | 
            +
                  when 'THROTTLE'
         | 
| 180 | 
            +
                    @throttle = Float(option_values[0])
         | 
| 181 | 
            +
                    @sleeper = Sleeper.new
         | 
| 182 | 
            +
                  when 'DISCARD_FILE_HEADER_BYTES'
         | 
| 183 | 
            +
                    @discard_file_header_bytes = Integer(option_values[0])
         | 
| 159 184 | 
             
                  end
         | 
| 160 185 | 
             
                end
         | 
| 161 186 |  | 
| @@ -173,11 +198,15 @@ module OpenC3 | |
| 173 198 | 
             
                end
         | 
| 174 199 |  | 
| 175 200 | 
             
                def get_next_telemetry_file
         | 
| 201 | 
            +
                  files = []
         | 
| 176 202 | 
             
                  if @recursive
         | 
| 177 | 
            -
                     | 
| 203 | 
            +
                    files = Dir.glob("#{@telemetry_read_folder}/**/*")
         | 
| 178 204 | 
             
                  else
         | 
| 179 | 
            -
                     | 
| 205 | 
            +
                    files = Dir.glob("#{@telemetry_read_folder}/*")
         | 
| 180 206 | 
             
                  end
         | 
| 207 | 
            +
                  # Dir.glob includes directories, so filter them out
         | 
| 208 | 
            +
                  files = files.sort.select { |fn| File.file?(fn) }
         | 
| 209 | 
            +
                  return files[0]
         | 
| 181 210 | 
             
                end
         | 
| 182 211 |  | 
| 183 212 | 
             
                def create_unique_filename
         | 
| @@ -14,7 +14,7 @@ | |
| 14 14 | 
             
            # GNU Affero General Public License for more details.
         | 
| 15 15 |  | 
| 16 16 | 
             
            # Modified by OpenC3, Inc.
         | 
| 17 | 
            -
            # All changes Copyright  | 
| 17 | 
            +
            # All changes Copyright 2025, OpenC3, Inc.
         | 
| 18 18 | 
             
            # All Rights Reserved
         | 
| 19 19 | 
             
            #
         | 
| 20 20 | 
             
            # This file may also be used under the terms of a commercial license
         | 
| @@ -37,7 +37,7 @@ module OpenC3 | |
| 37 37 | 
             
                # @param fill_fields [Boolean] Fill any required fields when writing packets
         | 
| 38 38 | 
             
                # @param allow_empty_data [true/false/nil] See Protocol#initialize
         | 
| 39 39 | 
             
                def initialize(discard_leading_bytes = 0, sync_pattern = nil, fill_fields = false, allow_empty_data = nil)
         | 
| 40 | 
            -
                  super(allow_empty_data)
         | 
| 40 | 
            +
                  super(allow_empty_data) # Calls reset()
         | 
| 41 41 | 
             
                  @discard_leading_bytes = discard_leading_bytes.to_i
         | 
| 42 42 | 
             
                  @sync_pattern = ConfigParser.handle_nil(sync_pattern)
         | 
| 43 43 | 
             
                  @sync_pattern = @sync_pattern.hex_to_byte_string if @sync_pattern
         | 
| @@ -65,7 +65,7 @@ module OpenC3 | |
| 65 65 | 
             
                  # @return [String|nil] Data for a packet consisting of the bytes read
         | 
| 66 66 | 
             
                  def read_data(data, extra = nil)
         | 
| 67 67 | 
             
                    @data << data
         | 
| 68 | 
            -
                    @extra = extra
         | 
| 68 | 
            +
                    @extra = extra unless (data.length == 0 and extra.nil?) # Maintain extra from last read read_data
         | 
| 69 69 |  | 
| 70 70 | 
             
                    while true
         | 
| 71 71 | 
             
                      control = handle_sync_pattern()
         | 
| @@ -14,7 +14,7 @@ | |
| 14 14 | 
             
            # GNU Affero General Public License for more details.
         | 
| 15 15 |  | 
| 16 16 | 
             
            # Modified by OpenC3, Inc.
         | 
| 17 | 
            -
            # All changes Copyright  | 
| 17 | 
            +
            # All changes Copyright 2025, OpenC3, Inc.
         | 
| 18 18 | 
             
            # All Rights Reserved
         | 
| 19 19 | 
             
            #
         | 
| 20 20 | 
             
            # This file may also be used under the terms of a commercial license
         | 
| @@ -30,12 +30,12 @@ module OpenC3 | |
| 30 30 |  | 
| 31 31 | 
             
                # @param sync_pattern (see BurstProtocol#initialize)
         | 
| 32 32 | 
             
                # @param max_length [Integer] The maximum allowed value of the length field
         | 
| 33 | 
            +
                # @param _unused [Integer] Legacy version number - unused
         | 
| 33 34 | 
             
                # @param allow_empty_data [true/false/nil] See Protocol#initialize
         | 
| 34 | 
            -
                def initialize(sync_pattern = nil, max_length = nil,  | 
| 35 | 
            +
                def initialize(sync_pattern = nil, max_length = nil, _unused = nil, allow_empty_data = nil)
         | 
| 35 36 | 
             
                  super(0, sync_pattern, false, allow_empty_data)
         | 
| 36 37 | 
             
                  @max_length = ConfigParser.handle_nil(max_length)
         | 
| 37 38 | 
             
                  @max_length = Integer(@max_length) if @max_length
         | 
| 38 | 
            -
                  @mode = Integer(mode)
         | 
| 39 39 | 
             
                end
         | 
| 40 40 |  | 
| 41 41 | 
             
                def reset
         | 
| @@ -47,13 +47,11 @@ module OpenC3 | |
| 47 47 | 
             
                  packet.received_time = @read_received_time
         | 
| 48 48 | 
             
                  packet.target_name = @read_target_name
         | 
| 49 49 | 
             
                  packet.packet_name = @read_packet_name
         | 
| 50 | 
            -
                   | 
| 51 | 
            -
             | 
| 52 | 
            -
                     | 
| 53 | 
            -
             | 
| 54 | 
            -
                     | 
| 55 | 
            -
                      packet.extra = @read_extra
         | 
| 56 | 
            -
                    end
         | 
| 50 | 
            +
                  packet.stored = @read_stored
         | 
| 51 | 
            +
                  if packet.extra and @read_extra
         | 
| 52 | 
            +
                    packet.extra.merge(@read_extra)
         | 
| 53 | 
            +
                  else
         | 
| 54 | 
            +
                    packet.extra = @read_extra
         | 
| 57 55 | 
             
                  end
         | 
| 58 56 | 
             
                  return packet
         | 
| 59 57 | 
             
                end
         | 
| @@ -67,14 +65,12 @@ module OpenC3 | |
| 67 65 | 
             
                  @write_target_name = 'UNKNOWN' unless @write_target_name
         | 
| 68 66 | 
             
                  @write_packet_name = packet.packet_name
         | 
| 69 67 | 
             
                  @write_packet_name = 'UNKNOWN' unless @write_packet_name
         | 
| 70 | 
            -
                   | 
| 71 | 
            -
             | 
| 72 | 
            -
             | 
| 73 | 
            -
             | 
| 74 | 
            -
                     | 
| 75 | 
            -
             | 
| 76 | 
            -
                      @write_extra = packet.extra.as_json(:allow_nan => true).to_json(:allow_nan => true)
         | 
| 77 | 
            -
                    end
         | 
| 68 | 
            +
                  @write_flags = 0
         | 
| 69 | 
            +
                  @write_flags |= COSMOS4_STORED_FLAG_MASK if packet.stored
         | 
| 70 | 
            +
                  @write_extra = nil
         | 
| 71 | 
            +
                  if packet.extra
         | 
| 72 | 
            +
                    @write_flags |= COSMOS4_EXTRA_FLAG_MASK
         | 
| 73 | 
            +
                    @write_extra = packet.extra.as_json(:allow_nan => true).to_json(:allow_nan => true)
         | 
| 78 74 | 
             
                  end
         | 
| 79 75 | 
             
                  return packet
         | 
| 80 76 | 
             
                end
         | 
| @@ -83,12 +79,10 @@ module OpenC3 | |
| 83 79 | 
             
                  data_length = [data.length].pack('N') # UINT32
         | 
| 84 80 | 
             
                  data_to_send = ''
         | 
| 85 81 | 
             
                  data_to_send << @sync_pattern if @sync_pattern
         | 
| 86 | 
            -
                   | 
| 87 | 
            -
             | 
| 88 | 
            -
                     | 
| 89 | 
            -
             | 
| 90 | 
            -
                      data_to_send << @write_extra
         | 
| 91 | 
            -
                    end
         | 
| 82 | 
            +
                  data_to_send << @write_flags
         | 
| 83 | 
            +
                  if @write_extra
         | 
| 84 | 
            +
                    data_to_send << [@write_extra.length].pack('N')
         | 
| 85 | 
            +
                    data_to_send << @write_extra
         | 
| 92 86 | 
             
                  end
         | 
| 93 87 | 
             
                  data_to_send << @write_time_seconds
         | 
| 94 88 | 
             
                  data_to_send << @write_time_microseconds
         | 
| @@ -146,7 +140,7 @@ module OpenC3 | |
| 146 140 | 
             
                    @reduction_state = :SYNC_REMOVED
         | 
| 147 141 | 
             
                  end
         | 
| 148 142 |  | 
| 149 | 
            -
                  if @reduction_state == :SYNC_REMOVED | 
| 143 | 
            +
                  if @reduction_state == :SYNC_REMOVED
         | 
| 150 144 | 
             
                    # Read and remove flags
         | 
| 151 145 | 
             
                    return :STOP if @data.length < 1
         | 
| 152 146 |  | 
| @@ -171,7 +165,7 @@ module OpenC3 | |
| 171 165 | 
             
                    @reduction_state = :FLAGS_REMOVED
         | 
| 172 166 | 
             
                  end
         | 
| 173 167 |  | 
| 174 | 
            -
                  if @reduction_state == :FLAGS_REMOVED | 
| 168 | 
            +
                  if @reduction_state == :FLAGS_REMOVED
         | 
| 175 169 | 
             
                    # Read and remove packet received time
         | 
| 176 170 | 
             
                    return :STOP if @data.length < 8
         | 
| 177 171 |  | 
    
        data/lib/openc3/interfaces.rb
    CHANGED
    
    | @@ -14,21 +14,22 @@ | |
| 14 14 | 
             
            # GNU Affero General Public License for more details.
         | 
| 15 15 |  | 
| 16 16 | 
             
            # Modified by OpenC3, Inc.
         | 
| 17 | 
            -
            # All changes Copyright  | 
| 17 | 
            +
            # All changes Copyright 2025, OpenC3, Inc.
         | 
| 18 18 | 
             
            # All Rights Reserved
         | 
| 19 19 | 
             
            #
         | 
| 20 20 | 
             
            # This file may also be used under the terms of a commercial license
         | 
| 21 21 | 
             
            # if purchased from OpenC3, Inc.
         | 
| 22 22 |  | 
| 23 23 | 
             
            module OpenC3
         | 
| 24 | 
            -
              autoload(: | 
| 24 | 
            +
              autoload(:FileInterface, 'openc3/interfaces/file_interface.rb')
         | 
| 25 25 | 
             
              autoload(:HttpClientInterface, 'openc3/interfaces/http_client_interface.rb')
         | 
| 26 26 | 
             
              autoload(:HttpServerInterface, 'openc3/interfaces/http_server_interface.rb')
         | 
| 27 | 
            +
              autoload(:Interface, 'openc3/interfaces/interface.rb')
         | 
| 27 28 | 
             
              autoload(:MqttInterface, 'openc3/interfaces/mqtt_interface.rb')
         | 
| 28 29 | 
             
              autoload(:MqttStreamInterface, 'openc3/interfaces/mqtt_stream_interface.rb')
         | 
| 29 | 
            -
              autoload(:StreamInterface, 'openc3/interfaces/stream_interface.rb')
         | 
| 30 30 | 
             
              autoload(:SerialInterface, 'openc3/interfaces/serial_interface.rb')
         | 
| 31 31 | 
             
              autoload(:SimulatedTargetInterface, 'openc3/interfaces/simulated_target_interface.rb')
         | 
| 32 | 
            +
              autoload(:StreamInterface, 'openc3/interfaces/stream_interface.rb')
         | 
| 32 33 | 
             
              autoload(:TcpipClientInterface, 'openc3/interfaces/tcpip_client_interface.rb')
         | 
| 33 34 | 
             
              autoload(:TcpipServerInterface, 'openc3/interfaces/tcpip_server_interface.rb')
         | 
| 34 35 | 
             
              autoload(:UdpInterface, 'openc3/interfaces/udp_interface.rb')
         | 
| @@ -14,7 +14,7 @@ | |
| 14 14 | 
             
            # GNU Affero General Public License for more details.
         | 
| 15 15 |  | 
| 16 16 | 
             
            # Modified by OpenC3, Inc.
         | 
| 17 | 
            -
            # All changes Copyright  | 
| 17 | 
            +
            # All changes Copyright 2025, OpenC3, Inc.
         | 
| 18 18 | 
             
            # All Rights Reserved
         | 
| 19 19 | 
             
            #
         | 
| 20 20 | 
             
            # This file may also be used under the terms of a commercial license
         | 
| @@ -100,7 +100,8 @@ module OpenC3 | |
| 100 100 | 
             
                  cycle_size = 1_000_000_000,
         | 
| 101 101 | 
             
                  cycle_hour = nil,
         | 
| 102 102 | 
             
                  cycle_minute = nil,
         | 
| 103 | 
            -
                  enforce_time_order = true
         | 
| 103 | 
            +
                  enforce_time_order = true,
         | 
| 104 | 
            +
                  cycle_thread: true
         | 
| 104 105 | 
             
                )
         | 
| 105 106 | 
             
                  @remote_log_directory = remote_log_directory
         | 
| 106 107 | 
             
                  @logging_enabled = ConfigParser.handle_true_false(logging_enabled)
         | 
| @@ -135,13 +136,15 @@ module OpenC3 | |
| 135 136 | 
             
                  # each time we create an entry which we do a LOT!
         | 
| 136 137 | 
             
                  @entry = String.new
         | 
| 137 138 |  | 
| 138 | 
            -
                   | 
| 139 | 
            -
             | 
| 140 | 
            -
                    @@ | 
| 139 | 
            +
                  if cycle_thread
         | 
| 140 | 
            +
                    # Always make sure there is a cycle thread - (because it does trimming)
         | 
| 141 | 
            +
                    @@mutex.synchronize do
         | 
| 142 | 
            +
                      @@instances << self
         | 
| 141 143 |  | 
| 142 | 
            -
             | 
| 143 | 
            -
             | 
| 144 | 
            -
             | 
| 144 | 
            +
                      unless @@cycle_thread
         | 
| 145 | 
            +
                        @@cycle_thread = OpenC3.safe_thread("Log cycle") do
         | 
| 146 | 
            +
                          cycle_thread_body()
         | 
| 147 | 
            +
                        end
         | 
| 145 148 | 
             
                      end
         | 
| 146 149 | 
             
                    end
         | 
| 147 150 | 
             
                  end
         | 
| @@ -14,7 +14,7 @@ | |
| 14 14 | 
             
            # GNU Affero General Public License for more details.
         | 
| 15 15 |  | 
| 16 16 | 
             
            # Modified by OpenC3, Inc.
         | 
| 17 | 
            -
            # All changes Copyright  | 
| 17 | 
            +
            # All changes Copyright 2025, OpenC3, Inc.
         | 
| 18 18 | 
             
            # All Rights Reserved
         | 
| 19 19 | 
             
            #
         | 
| 20 20 | 
             
            # This file may also be used under the terms of a commercial license
         | 
| @@ -309,7 +309,7 @@ module OpenC3 | |
| 309 309 | 
             
                    if cbor
         | 
| 310 310 | 
             
                      extra = CBOR.decode(extra_encoded)
         | 
| 311 311 | 
             
                    else
         | 
| 312 | 
            -
                      extra = JSON.parse( | 
| 312 | 
            +
                      extra = JSON.parse(extra_encoded, allow_nan: true, create_additions: true)
         | 
| 313 313 | 
             
                    end
         | 
| 314 314 | 
             
                  end
         | 
| 315 315 | 
             
                  data = entry[next_offset..-1]
         | 
| @@ -14,7 +14,7 @@ | |
| 14 14 | 
             
            # GNU Affero General Public License for more details.
         | 
| 15 15 |  | 
| 16 16 | 
             
            # Modified by OpenC3, Inc.
         | 
| 17 | 
            -
            # All changes Copyright  | 
| 17 | 
            +
            # All changes Copyright 2025, OpenC3, Inc.
         | 
| 18 18 | 
             
            # All Rights Reserved
         | 
| 19 19 | 
             
            #
         | 
| 20 20 | 
             
            # This file may also be used under the terms of a commercial license
         | 
| @@ -208,9 +208,10 @@ module OpenC3 | |
| 208 208 | 
             
                  shutdown()
         | 
| 209 209 | 
             
                end
         | 
| 210 210 |  | 
| 211 | 
            -
                def shutdown
         | 
| 211 | 
            +
                def shutdown(state = 'STOPPED')
         | 
| 212 212 | 
             
                  return if @shutdown_complete
         | 
| 213 213 | 
             
                  @logger.info("Shutting down microservice: #{@name}")
         | 
| 214 | 
            +
                  @state = state
         | 
| 214 215 | 
             
                  @cancel_thread = true
         | 
| 215 216 | 
             
                  @microservice_status_sleeper.cancel if @microservice_status_sleeper
         | 
| 216 217 | 
             
                  MicroserviceStatusModel.set(as_json(:allow_nan => true), scope: @scope)
         | 
    
        data/lib/openc3/models/model.rb
    CHANGED
    
    | @@ -139,7 +139,7 @@ module OpenC3 | |
| 139 139 |  | 
| 140 140 | 
             
                # Update the Redis hash at primary_key and set the field "name"
         | 
| 141 141 | 
             
                # to the JSON generated via calling as_json
         | 
| 142 | 
            -
                def create(update: false, force: false, queued: false)
         | 
| 142 | 
            +
                def create(update: false, force: false, queued: false, isoformat: false)
         | 
| 143 143 | 
             
                  unless force
         | 
| 144 144 | 
             
                    existing = self.class.store.hget(@primary_key, @name)
         | 
| 145 145 | 
             
                    if existing
         | 
| @@ -148,7 +148,11 @@ module OpenC3 | |
| 148 148 | 
             
                      raise "#{@primary_key}:#{@name} doesn't exist at update" if update
         | 
| 149 149 | 
             
                    end
         | 
| 150 150 | 
             
                  end
         | 
| 151 | 
            -
                   | 
| 151 | 
            +
                  if isoformat
         | 
| 152 | 
            +
                    @updated_at = Time.now.utc.iso8601
         | 
| 153 | 
            +
                  else
         | 
| 154 | 
            +
                    @updated_at = Time.now.utc.to_nsec_from_epoch
         | 
| 155 | 
            +
                  end
         | 
| 152 156 |  | 
| 153 157 | 
             
                  if queued
         | 
| 154 158 | 
             
                    write_store = self.class.store_queued
         | 
| @@ -0,0 +1,242 @@ | |
| 1 | 
            +
            # encoding: ascii-8bit
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            # Copyright 2025 OpenC3, Inc.
         | 
| 4 | 
            +
            # All Rights Reserved.
         | 
| 5 | 
            +
            #
         | 
| 6 | 
            +
            # This program is free software; you can modify and/or redistribute it
         | 
| 7 | 
            +
            # under the terms of the GNU Affero General Public License
         | 
| 8 | 
            +
            # as published by the Free Software Foundation; version 3 with
         | 
| 9 | 
            +
            # attribution addendums as found in the LICENSE.txt
         | 
| 10 | 
            +
            #
         | 
| 11 | 
            +
            # This program is distributed in the hope that it will be useful,
         | 
| 12 | 
            +
            # but WITHOUT ANY WARRANTY; without even the implied warranty of
         | 
| 13 | 
            +
            # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
         | 
| 14 | 
            +
            # GNU Affero General Public License for more details.
         | 
| 15 | 
            +
            #
         | 
| 16 | 
            +
            # This file may also be used under the terms of a commercial license
         | 
| 17 | 
            +
            # if purchased from OpenC3, Inc.
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            require 'openc3/models/model'
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            module OpenC3
         | 
| 22 | 
            +
              class ScriptStatusModel < Model
         | 
| 23 | 
            +
                # Note: ScriptRunner only has permissions for keys that start with running-script
         | 
| 24 | 
            +
                RUNNING_PRIMARY_KEY = 'running-script'
         | 
| 25 | 
            +
                COMPLETED_PRIMARY_KEY = 'running-script-completed'
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def id
         | 
| 28 | 
            +
                  return @name
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
                attr_reader :state # spawning, init, running, paused, waiting, breakpoint, error, crashed, stopped, completed, completed_errors, killed
         | 
| 31 | 
            +
                attr_accessor :shard
         | 
| 32 | 
            +
                attr_accessor :filename
         | 
| 33 | 
            +
                attr_accessor :current_filename
         | 
| 34 | 
            +
                attr_accessor :line_no
         | 
| 35 | 
            +
                attr_accessor :start_line_no
         | 
| 36 | 
            +
                attr_accessor :end_line_no
         | 
| 37 | 
            +
                attr_accessor :username
         | 
| 38 | 
            +
                attr_accessor :user_full_name
         | 
| 39 | 
            +
                attr_accessor :start_time
         | 
| 40 | 
            +
                attr_accessor :end_time
         | 
| 41 | 
            +
                attr_accessor :disconnect
         | 
| 42 | 
            +
                attr_accessor :environment
         | 
| 43 | 
            +
                attr_accessor :suite_runner
         | 
| 44 | 
            +
                attr_accessor :errors
         | 
| 45 | 
            +
                attr_accessor :pid
         | 
| 46 | 
            +
                attr_accessor :log
         | 
| 47 | 
            +
                attr_accessor :report
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                # NOTE: The following three class methods are used by the ModelController
         | 
| 50 | 
            +
                # and are reimplemented to enable various Model class methods to work
         | 
| 51 | 
            +
                def self.get(name:, scope:, type: "auto")
         | 
| 52 | 
            +
                  if type == "auto" or type == "running"
         | 
| 53 | 
            +
                    # Check for running first
         | 
| 54 | 
            +
                    running = super("#{RUNNING_PRIMARY_KEY}__#{scope}", name: name)
         | 
| 55 | 
            +
                    return running if running
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
                  return super("#{COMPLETED_PRIMARY_KEY}__#{scope}", name: name)
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                def self.names(scope:, type: "running")
         | 
| 61 | 
            +
                  if type == "running"
         | 
| 62 | 
            +
                    return super("#{RUNNING_PRIMARY_KEY}__#{scope}")
         | 
| 63 | 
            +
                  else
         | 
| 64 | 
            +
                    return super("#{COMPLETED_PRIMARY_KEY}__#{scope}")
         | 
| 65 | 
            +
                  end
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                def self.all(scope:, offset: 0, limit: 10, type: "running")
         | 
| 69 | 
            +
                  if type == "running"
         | 
| 70 | 
            +
                    keys = self.store.zrevrange("#{RUNNING_PRIMARY_KEY}__#{scope}__LIST", offset.to_i, offset.to_i + limit.to_i - 1)
         | 
| 71 | 
            +
                    return [] if keys.empty?
         | 
| 72 | 
            +
                    result = self.store.redis_pool.pipelined do
         | 
| 73 | 
            +
                      keys.each do |key|
         | 
| 74 | 
            +
                        self.store.hget("#{RUNNING_PRIMARY_KEY}__#{scope}", key)
         | 
| 75 | 
            +
                      end
         | 
| 76 | 
            +
                    end
         | 
| 77 | 
            +
                    result = result.map do |r|
         | 
| 78 | 
            +
                      if r.nil?
         | 
| 79 | 
            +
                        nil
         | 
| 80 | 
            +
                      else
         | 
| 81 | 
            +
                        JSON.parse(r, :allow_nan => true, :create_additions => true)
         | 
| 82 | 
            +
                      end
         | 
| 83 | 
            +
                    end
         | 
| 84 | 
            +
                    return result
         | 
| 85 | 
            +
                  else
         | 
| 86 | 
            +
                    keys = self.store.zrevrange("#{COMPLETED_PRIMARY_KEY}__#{scope}__LIST", offset.to_i, offset.to_i + limit.to_i - 1)
         | 
| 87 | 
            +
                    return [] if keys.empty?
         | 
| 88 | 
            +
                    result = self.store.redis_pool.pipelined do
         | 
| 89 | 
            +
                      keys.each do |key|
         | 
| 90 | 
            +
                        self.store.hget("#{COMPLETED_PRIMARY_KEY}__#{scope}", key)
         | 
| 91 | 
            +
                      end
         | 
| 92 | 
            +
                    end
         | 
| 93 | 
            +
                    result = result.map do |r|
         | 
| 94 | 
            +
                      if r.nil?
         | 
| 95 | 
            +
                        nil
         | 
| 96 | 
            +
                      else
         | 
| 97 | 
            +
                        JSON.parse(r, :allow_nan => true, :create_additions => true)
         | 
| 98 | 
            +
                      end
         | 
| 99 | 
            +
                    end
         | 
| 100 | 
            +
                    return result
         | 
| 101 | 
            +
                  end
         | 
| 102 | 
            +
                end
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                def self.count(scope:, type: "running")
         | 
| 105 | 
            +
                  if type == "running"
         | 
| 106 | 
            +
                    return self.store.zcount("#{RUNNING_PRIMARY_KEY}__#{scope}__LIST", 0, Float::INFINITY)
         | 
| 107 | 
            +
                  else
         | 
| 108 | 
            +
                    return self.store.zcount("#{COMPLETED_PRIMARY_KEY}__#{scope}__LIST", 0, Float::INFINITY)
         | 
| 109 | 
            +
                  end
         | 
| 110 | 
            +
                end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                def initialize(
         | 
| 113 | 
            +
                  name:, # id
         | 
| 114 | 
            +
                  state:, # spawning, init, running, paused, waiting, error, breakpoint, crashed, stopped, completed, completed_errors, killed
         | 
| 115 | 
            +
                  shard: 0, # Future enhancement of script runner shards
         | 
| 116 | 
            +
                  filename:, # The initial filename
         | 
| 117 | 
            +
                  current_filename: nil, # The current filename
         | 
| 118 | 
            +
                  line_no: 0, # The current line number
         | 
| 119 | 
            +
                  start_line_no: 1, # The line number to start the script at
         | 
| 120 | 
            +
                  end_line_no: nil, # The line number to end the script at
         | 
| 121 | 
            +
                  username:, # The username of the person who started the script
         | 
| 122 | 
            +
                  user_full_name:, # The full name of the person who started the script
         | 
| 123 | 
            +
                  start_time:, # The time the script started ISO format
         | 
| 124 | 
            +
                  end_time: nil, # The time the script ended ISO format
         | 
| 125 | 
            +
                  disconnect: false,
         | 
| 126 | 
            +
                  environment: nil,
         | 
| 127 | 
            +
                  suite_runner: nil,
         | 
| 128 | 
            +
                  errors: nil,
         | 
| 129 | 
            +
                  pid: nil,
         | 
| 130 | 
            +
                  log: nil,
         | 
| 131 | 
            +
                  report: nil,
         | 
| 132 | 
            +
                  updated_at: nil,
         | 
| 133 | 
            +
                  scope:
         | 
| 134 | 
            +
                )
         | 
| 135 | 
            +
                  @state = state
         | 
| 136 | 
            +
                  if is_complete?()
         | 
| 137 | 
            +
                    super("#{COMPLETED_PRIMARY_KEY}__#{scope}", name: name, updated_at: updated_at, plugin: nil, scope: scope)
         | 
| 138 | 
            +
                  else
         | 
| 139 | 
            +
                    super("#{RUNNING_PRIMARY_KEY}__#{scope}", name: name, updated_at: updated_at, plugin: nil, scope: scope)
         | 
| 140 | 
            +
                  end
         | 
| 141 | 
            +
                  @shard = shard.to_i
         | 
| 142 | 
            +
                  @filename = filename
         | 
| 143 | 
            +
                  @current_filename = current_filename
         | 
| 144 | 
            +
                  @line_no = line_no
         | 
| 145 | 
            +
                  @start_line_no = start_line_no
         | 
| 146 | 
            +
                  @end_line_no = end_line_no
         | 
| 147 | 
            +
                  @username = username
         | 
| 148 | 
            +
                  @user_full_name = user_full_name
         | 
| 149 | 
            +
                  @start_time = start_time
         | 
| 150 | 
            +
                  @end_time = end_time
         | 
| 151 | 
            +
                  @disconnect = disconnect
         | 
| 152 | 
            +
                  @environment = environment
         | 
| 153 | 
            +
                  @suite_runner = suite_runner
         | 
| 154 | 
            +
                  @errors = errors
         | 
| 155 | 
            +
                  @pid = pid
         | 
| 156 | 
            +
                  @log = log
         | 
| 157 | 
            +
                  @report = report
         | 
| 158 | 
            +
                end
         | 
| 159 | 
            +
             | 
| 160 | 
            +
                def is_complete?
         | 
| 161 | 
            +
                  return (@state == 'completed' or @state == 'completed_errors' or @state == 'stopped' or @state == 'crashed' or @state == 'killed')
         | 
| 162 | 
            +
                end
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                def state=(new_state)
         | 
| 165 | 
            +
                  # If the state is already a flavor of complete, leave it alone (first wins)
         | 
| 166 | 
            +
                  if not is_complete?()
         | 
| 167 | 
            +
                    @state = new_state
         | 
| 168 | 
            +
                    # If setting to complete, check for errors
         | 
| 169 | 
            +
                    # and set the state to complete_errors if they exist
         | 
| 170 | 
            +
                    if @state == 'completed' and @errors
         | 
| 171 | 
            +
                      @state = 'completed_errors'
         | 
| 172 | 
            +
                    end
         | 
| 173 | 
            +
                  end
         | 
| 174 | 
            +
                end
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                # Update the Redis hash at primary_key and set the field "name"
         | 
| 177 | 
            +
                # to the JSON generated via calling as_json
         | 
| 178 | 
            +
                def create(update: false, force: false, queued: false, isoformat: true)
         | 
| 179 | 
            +
                  @updated_at = Time.now.utc.to_nsec_from_epoch
         | 
| 180 | 
            +
             | 
| 181 | 
            +
                  if queued
         | 
| 182 | 
            +
                    write_store = self.class.store_queued
         | 
| 183 | 
            +
                  else
         | 
| 184 | 
            +
                    write_store = self.class.store
         | 
| 185 | 
            +
                  end
         | 
| 186 | 
            +
                  write_store.hset(@primary_key, @name, JSON.generate(self.as_json(:allow_nan => true), :allow_nan => true))
         | 
| 187 | 
            +
             | 
| 188 | 
            +
                  # Also add to ordered set on create
         | 
| 189 | 
            +
                  write_store.zadd(@primary_key + "__LIST", @name.to_i, @name) if not update
         | 
| 190 | 
            +
                end
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                def update(force: false, queued: false)
         | 
| 193 | 
            +
                  # Magically handle the change from running to completed
         | 
| 194 | 
            +
                  if is_complete?() and @primary_key == "#{RUNNING_PRIMARY_KEY}__#{@scope}"
         | 
| 195 | 
            +
                    # Destroy the running key
         | 
| 196 | 
            +
                    destroy()
         | 
| 197 | 
            +
                    @destroyed = false
         | 
| 198 | 
            +
             | 
| 199 | 
            +
                    # Move to completed
         | 
| 200 | 
            +
                    @primary_key = "#{COMPLETED_PRIMARY_KEY}__#{@scope}"
         | 
| 201 | 
            +
                    create(update: false, force: force, queued: queued, isoformat: true)
         | 
| 202 | 
            +
                  else
         | 
| 203 | 
            +
                    create(update: true, force: force, queued: queued, isoformat: true)
         | 
| 204 | 
            +
                  end
         | 
| 205 | 
            +
                end
         | 
| 206 | 
            +
             | 
| 207 | 
            +
                # Delete the model from the Store
         | 
| 208 | 
            +
                def destroy
         | 
| 209 | 
            +
                  @destroyed = true
         | 
| 210 | 
            +
                  undeploy()
         | 
| 211 | 
            +
                  self.class.store.hdel(@primary_key, @name)
         | 
| 212 | 
            +
                  # Also remove from ordered set
         | 
| 213 | 
            +
                  self.class.store.zremrangebyscore(@primary_key + "__LIST", @name.to_i, @name.to_i)
         | 
| 214 | 
            +
                end
         | 
| 215 | 
            +
             | 
| 216 | 
            +
                def as_json(*a)
         | 
| 217 | 
            +
                  {
         | 
| 218 | 
            +
                    'name' => @name,
         | 
| 219 | 
            +
                    'state' => @state,
         | 
| 220 | 
            +
                    'shard' => @shard,
         | 
| 221 | 
            +
                    'filename' => @filename,
         | 
| 222 | 
            +
                    'current_filename' => @current_filename,
         | 
| 223 | 
            +
                    'line_no' => @line_no,
         | 
| 224 | 
            +
                    'start_line_no' => @start_line_no,
         | 
| 225 | 
            +
                    'end_line_no' => @end_line_no,
         | 
| 226 | 
            +
                    'username' => @username,
         | 
| 227 | 
            +
                    'user_full_name' => @user_full_name,
         | 
| 228 | 
            +
                    'start_time' => @start_time,
         | 
| 229 | 
            +
                    'end_time' => @end_time,
         | 
| 230 | 
            +
                    'disconnect' => @disconnect,
         | 
| 231 | 
            +
                    'environment' => @environment,
         | 
| 232 | 
            +
                    'suite_runner' => @suite_runner,
         | 
| 233 | 
            +
                    'errors' => @errors,
         | 
| 234 | 
            +
                    'pid' => @pid,
         | 
| 235 | 
            +
                    'log' => @log,
         | 
| 236 | 
            +
                    'report' => @report,
         | 
| 237 | 
            +
                    'updated_at' => @updated_at,
         | 
| 238 | 
            +
                    'scope' => @scope
         | 
| 239 | 
            +
                  }
         | 
| 240 | 
            +
                end
         | 
| 241 | 
            +
              end
         | 
| 242 | 
            +
            end
         | 
| @@ -503,6 +503,10 @@ module OpenC3 | |
| 503 503 | 
             
                  !cached
         | 
| 504 504 | 
             
                end
         | 
| 505 505 |  | 
| 506 | 
            +
                def goto(line_no_or_procedure_name, line_no = nil)
         | 
| 507 | 
            +
                  raise "goto is not supported outside of ScriptRunner"
         | 
| 508 | 
            +
                end
         | 
| 509 | 
            +
             | 
| 506 510 | 
             
                # Require an additional ruby file
         | 
| 507 511 | 
             
                def load_utility(procedure_name)
         | 
| 508 512 | 
             
                  return start(procedure_name)
         | 
| @@ -167,17 +167,17 @@ module OpenC3 | |
| 167 167 | 
             
                  end
         | 
| 168 168 | 
             
                end
         | 
| 169 169 |  | 
| 170 | 
            -
                def running_script_list(scope: $openc3_scope)
         | 
| 170 | 
            +
                def running_script_list(limit: 10, offset: 0, scope: $openc3_scope)
         | 
| 171 171 | 
             
                  endpoint = "/script-api/running-script"
         | 
| 172 | 
            -
                  response = $script_runner_api_server.request('get', endpoint, scope: scope)
         | 
| 172 | 
            +
                  response = $script_runner_api_server.request('get', endpoint, query: {"limit" => limit, "offset" => offset}, scope: scope)
         | 
| 173 173 | 
             
                  if response.nil? || response.status != 200
         | 
| 174 174 | 
             
                    _script_response_error(response, "Running script list request failed", scope: scope)
         | 
| 175 175 | 
             
                  else
         | 
| 176 | 
            -
                    return JSON.parse(response.body, :allow_nan => true, :create_additions => true)
         | 
| 176 | 
            +
                    return JSON.parse(response.body, :allow_nan => true, :create_additions => true)['items']
         | 
| 177 177 | 
             
                  end
         | 
| 178 178 | 
             
                end
         | 
| 179 179 |  | 
| 180 | 
            -
                def  | 
| 180 | 
            +
                def script_get(id, scope: $openc3_scope)
         | 
| 181 181 | 
             
                  endpoint = "/script-api/running-script/#{id}"
         | 
| 182 182 | 
             
                  response = $script_runner_api_server.request('get', endpoint, scope: scope)
         | 
| 183 183 | 
             
                  if response.nil? || response.status != 200
         | 
| @@ -186,6 +186,7 @@ module OpenC3 | |
| 186 186 | 
             
                    return JSON.parse(response.body, :allow_nan => true, :create_additions => true)
         | 
| 187 187 | 
             
                  end
         | 
| 188 188 | 
             
                end
         | 
| 189 | 
            +
                alias running_script_get script_get # Deprecated alias for compatibility
         | 
| 189 190 |  | 
| 190 191 | 
             
                def _running_script_action(id, action_name, scope: $openc3_scope)
         | 
| 191 192 | 
             
                  endpoint = "/script-api/running-script/#{id}/#{action_name}"
         | 
| @@ -249,13 +250,27 @@ module OpenC3 | |
| 249 250 | 
             
                  end
         | 
| 250 251 | 
             
                end
         | 
| 251 252 |  | 
| 252 | 
            -
                def  | 
| 253 | 
            +
                def running_script_execute_while_paused(id, filename, line_no, end_line_no = nil, scope: $openc3_scope)
         | 
| 254 | 
            +
                  endpoint = "/script-api/running-script/#{id}/executewhilepaused"
         | 
| 255 | 
            +
                  if end_line_no
         | 
| 256 | 
            +
                    response = $script_runner_api_server.request('post', endpoint, json: true, data: {'args' => [filename, line_no, end_line_no]}, scope: scope)
         | 
| 257 | 
            +
                  else
         | 
| 258 | 
            +
                    response = $script_runner_api_server.request('post', endpoint, json: true, data: {'args' => [filename, line_no]}, scope: scope)
         | 
| 259 | 
            +
                  end
         | 
| 260 | 
            +
                  if response.nil? || response.status != 200
         | 
| 261 | 
            +
                    _script_response_error(response, "Running script executewhilepaused request failed", scope: scope)
         | 
| 262 | 
            +
                  else
         | 
| 263 | 
            +
                    return true
         | 
| 264 | 
            +
                  end
         | 
| 265 | 
            +
                end
         | 
| 266 | 
            +
             | 
| 267 | 
            +
                def completed_script_list(limit: 10, offset: 0, scope: $openc3_scope)
         | 
| 253 268 | 
             
                  endpoint = "/script-api/completed-scripts"
         | 
| 254 | 
            -
                  response = $script_runner_api_server.request('get', endpoint, scope: scope)
         | 
| 269 | 
            +
                  response = $script_runner_api_server.request('get', endpoint, query: {"limit" => limit, "offset" => offset}, scope: scope)
         | 
| 255 270 | 
             
                  if response.nil? || response.status != 200
         | 
| 256 271 | 
             
                    _script_response_error(response, "Completed script list request failed", scope: scope)
         | 
| 257 272 | 
             
                  else
         | 
| 258 | 
            -
                    return JSON.parse(response.body, :allow_nan => true, :create_additions => true)
         | 
| 273 | 
            +
                    return JSON.parse(response.body, :allow_nan => true, :create_additions => true)['items']
         | 
| 259 274 | 
             
                  end
         | 
| 260 275 | 
             
                end
         | 
| 261 276 | 
             
              end
         | 
| @@ -29,7 +29,7 @@ module OpenC3 | |
| 29 29 | 
             
              class OpenC3AuthenticationError < StandardError; end
         | 
| 30 30 | 
             
              class OpenC3AuthenticationRetryableError < OpenC3AuthenticationError; end
         | 
| 31 31 |  | 
| 32 | 
            -
              # OpenC3  | 
| 32 | 
            +
              # OpenC3 COSMOS Core authentication code
         | 
| 33 33 | 
             
              class OpenC3Authentication
         | 
| 34 34 | 
             
                def initialize()
         | 
| 35 35 | 
             
                  @token = ENV['OPENC3_API_PASSWORD']
         | 
| @@ -64,6 +64,7 @@ module OpenC3 | |
| 64 64 |  | 
| 65 65 | 
             
                # Closes the message log and marks it read only
         | 
| 66 66 | 
             
                def stop(take_mutex = true, metadata: {})
         | 
| 67 | 
            +
                  bucket_key = nil
         | 
| 67 68 | 
             
                  @mutex.lock if take_mutex
         | 
| 68 69 | 
             
                  if @file and not @file.closed?
         | 
| 69 70 | 
             
                    @file.close
         | 
| @@ -77,6 +78,7 @@ module OpenC3 | |
| 77 78 | 
             
                    end
         | 
| 78 79 | 
             
                  end
         | 
| 79 80 | 
             
                  @mutex.unlock if take_mutex
         | 
| 81 | 
            +
                  return bucket_key
         | 
| 80 82 | 
             
                end
         | 
| 81 83 |  | 
| 82 84 | 
             
                # Creates a new message log and sets the filename
         | 
    
        data/lib/openc3/version.rb
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            # encoding: ascii-8bit
         | 
| 2 2 |  | 
| 3 | 
            -
            OPENC3_VERSION = '6. | 
| 3 | 
            +
            OPENC3_VERSION = '6.4.0'
         | 
| 4 4 | 
             
            module OpenC3
         | 
| 5 5 | 
             
              module Version
         | 
| 6 6 | 
             
                MAJOR = '6'
         | 
| 7 | 
            -
                MINOR = ' | 
| 7 | 
            +
                MINOR = '4'
         | 
| 8 8 | 
             
                PATCH = '0'
         | 
| 9 9 | 
             
                OTHER = ''
         | 
| 10 | 
            -
                BUILD = ' | 
| 10 | 
            +
                BUILD = '7f674e6c8a5d1e77fc64182365b831a17751b78d'
         | 
| 11 11 | 
             
              end
         | 
| 12 | 
            -
              VERSION = '6. | 
| 13 | 
            -
              GEM_VERSION = '6. | 
| 12 | 
            +
              VERSION = '6.4.0'
         | 
| 13 | 
            +
              GEM_VERSION = '6.4.0'
         | 
| 14 14 | 
             
            end
         | 
| @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            {
         | 
| 2 2 | 
             
              "name": "<%= tool_name %>",
         | 
| 3 | 
            -
              "version": "6. | 
| 3 | 
            +
              "version": "6.4.0",
         | 
| 4 4 | 
             
              "scripts": {
         | 
| 5 5 | 
             
                "ng": "ng",
         | 
| 6 6 | 
             
                "start": "ng serve",
         | 
| @@ -23,7 +23,7 @@ | |
| 23 23 | 
             
                "@angular/platform-browser-dynamic": "^18.2.6",
         | 
| 24 24 | 
             
                "@angular/router": "^18.2.6",
         | 
| 25 25 | 
             
                "@astrouxds/astro-web-components": "^7.24.0",
         | 
| 26 | 
            -
                "@openc3/js-common": "6. | 
| 26 | 
            +
                "@openc3/js-common": "6.4.0",
         | 
| 27 27 | 
             
                "rxjs": "~7.8.0",
         | 
| 28 28 | 
             
                "single-spa": "^5.9.5",
         | 
| 29 29 | 
             
                "single-spa-angular": "^9.2.0",
         | 
| @@ -1,30 +1,11 @@ | |
| 1 | 
            -
            import  | 
| 2 | 
            -
            import  | 
| 3 | 
            -
            import  | 
| 4 | 
            -
            import  | 
| 5 | 
            -
            import { fileURLToPath } from 'node:url'
         | 
| 6 | 
            -
            import js from '@eslint/js'
         | 
| 7 | 
            -
            import { FlatCompat } from '@eslint/eslintrc'
         | 
| 8 | 
            -
             | 
| 9 | 
            -
            const __filename = fileURLToPath(import.meta.url)
         | 
| 10 | 
            -
            const __dirname = path.dirname(__filename)
         | 
| 11 | 
            -
            const compat = new FlatCompat({
         | 
| 12 | 
            -
              baseDirectory: __dirname,
         | 
| 13 | 
            -
              recommendedConfig: js.configs.recommended,
         | 
| 14 | 
            -
              allConfig: js.configs.all,
         | 
| 15 | 
            -
            })
         | 
| 1 | 
            +
            import prettierConfig from "@vue/eslint-config-prettier"
         | 
| 2 | 
            +
            import pluginVue from "eslint-plugin-vue"
         | 
| 3 | 
            +
            import globals from "globals"
         | 
| 4 | 
            +
            import parser from "vue-eslint-parser"
         | 
| 16 5 |  | 
| 17 6 | 
             
            export default [
         | 
| 18 | 
            -
              ... | 
| 19 | 
            -
                'plugin:vue/vue3-essential',
         | 
| 20 | 
            -
                'plugin:prettier/recommended',
         | 
| 21 | 
            -
                '@vue/prettier',
         | 
| 22 | 
            -
              ),
         | 
| 7 | 
            +
              ...pluginVue.configs['flat/recommended'],
         | 
| 23 8 | 
             
              {
         | 
| 24 | 
            -
                plugins: {
         | 
| 25 | 
            -
                  prettier,
         | 
| 26 | 
            -
                },
         | 
| 27 | 
            -
             | 
| 28 9 | 
             
                languageOptions: {
         | 
| 29 10 | 
             
                  globals: {
         | 
| 30 11 | 
             
                    ...globals.node,
         | 
| @@ -32,32 +13,26 @@ export default [ | |
| 32 13 |  | 
| 33 14 | 
             
                  parser: parser,
         | 
| 34 15 | 
             
                  ecmaVersion: 2022,
         | 
| 35 | 
            -
                  sourceType:  | 
| 16 | 
            +
                  sourceType: "module",
         | 
| 36 17 | 
             
                },
         | 
| 37 18 |  | 
| 38 19 | 
             
                rules: {
         | 
| 39 | 
            -
                   | 
| 40 | 
            -
                   | 
| 20 | 
            +
                  "no-console": "error",
         | 
| 21 | 
            +
                  "no-debugger": "error",
         | 
| 41 22 |  | 
| 42 | 
            -
                   | 
| 43 | 
            -
                     | 
| 44 | 
            -
             | 
| 45 | 
            -
                      endOfLine: 'auto',
         | 
| 46 | 
            -
                    },
         | 
| 47 | 
            -
                  ],
         | 
| 23 | 
            +
                  "prettier/prettier": ["warn", {
         | 
| 24 | 
            +
                    endOfLine: "auto",
         | 
| 25 | 
            +
                  }],
         | 
| 48 26 |  | 
| 49 | 
            -
                   | 
| 27 | 
            +
                  "vue/multi-word-component-names": "off",
         | 
| 50 28 |  | 
| 51 | 
            -
                   | 
| 52 | 
            -
                     | 
| 53 | 
            -
             | 
| 54 | 
            -
                      allowModifiers: true,
         | 
| 55 | 
            -
                    },
         | 
| 56 | 
            -
                  ],
         | 
| 29 | 
            +
                  "vue/valid-v-slot": ["error", {
         | 
| 30 | 
            +
                    allowModifiers: true,
         | 
| 31 | 
            +
                  }],
         | 
| 57 32 | 
             
                },
         | 
| 58 33 | 
             
              },
         | 
| 59 34 | 
             
              {
         | 
| 60 | 
            -
                files: [ | 
| 35 | 
            +
                files: ["**/__tests__/*.{j,t}s?(x)", "**/tests/unit/**/*.spec.{j,t}s?(x)"],
         | 
| 61 36 |  | 
| 62 37 | 
             
                languageOptions: {
         | 
| 63 38 | 
             
                  globals: {
         | 
| @@ -65,4 +40,5 @@ export default [ | |
| 65 40 | 
             
                  },
         | 
| 66 41 | 
             
                },
         | 
| 67 42 | 
             
              },
         | 
| 43 | 
            +
              prettierConfig,
         | 
| 68 44 | 
             
            ]
         | 
| @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            {
         | 
| 2 2 | 
             
              "name": "<%= tool_name %>",
         | 
| 3 | 
            -
              "version": "6. | 
| 3 | 
            +
              "version": "6.4.0",
         | 
| 4 4 | 
             
              "private": true,
         | 
| 5 5 | 
             
              "type": "module",
         | 
| 6 6 | 
             
              "scripts": {
         | 
| @@ -11,8 +11,8 @@ | |
| 11 11 | 
             
              },
         | 
| 12 12 | 
             
              "dependencies": {
         | 
| 13 13 | 
             
                "@astrouxds/astro-web-components": "^7.24.0",
         | 
| 14 | 
            -
                "@openc3/js-common": "6. | 
| 15 | 
            -
                "@openc3/vue-common": "6. | 
| 14 | 
            +
                "@openc3/js-common": "6.4.0",
         | 
| 15 | 
            +
                "@openc3/vue-common": "6.4.0",
         | 
| 16 16 | 
             
                "axios": "^1.7.7",
         | 
| 17 17 | 
             
                "date-fns": "^4.1.0",
         | 
| 18 18 | 
             
                "lodash": "^4.17.21",
         | 
| @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            {
         | 
| 2 2 | 
             
              "name": "<%= widget_name %>",
         | 
| 3 | 
            -
              "version": "6. | 
| 3 | 
            +
              "version": "6.4.0",
         | 
| 4 4 | 
             
              "private": true,
         | 
| 5 5 | 
             
              "type": "module",
         | 
| 6 6 | 
             
              "scripts": {
         | 
| @@ -8,7 +8,7 @@ | |
| 8 8 | 
             
              },
         | 
| 9 9 | 
             
              "dependencies": {
         | 
| 10 10 | 
             
                "@astrouxds/astro-web-components": "^7.24.0",
         | 
| 11 | 
            -
                "@openc3/vue-common": "6. | 
| 11 | 
            +
                "@openc3/vue-common": "6.4.0",
         | 
| 12 12 | 
             
                "vuetify": "^3.7.1"
         | 
| 13 13 | 
             
              },
         | 
| 14 14 | 
             
              "devDependencies": {
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: openc3
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 6. | 
| 4 | 
            +
              version: 6.4.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Ryan Melton
         | 
| @@ -86,14 +86,14 @@ dependencies: | |
| 86 86 | 
             
                requirements:
         | 
| 87 87 | 
             
                - - "~>"
         | 
| 88 88 | 
             
                  - !ruby/object:Gem::Version
         | 
| 89 | 
            -
                    version: 1.3 | 
| 89 | 
            +
                    version: '1.3'
         | 
| 90 90 | 
             
              type: :runtime
         | 
| 91 91 | 
             
              prerelease: false
         | 
| 92 92 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 93 93 | 
             
                requirements:
         | 
| 94 94 | 
             
                - - "~>"
         | 
| 95 95 | 
             
                  - !ruby/object:Gem::Version
         | 
| 96 | 
            -
                    version: 1.3 | 
| 96 | 
            +
                    version: '1.3'
         | 
| 97 97 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 98 98 | 
             
              name: psych
         | 
| 99 99 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -1033,6 +1033,7 @@ files: | |
| 1033 1033 | 
             
            - lib/openc3/models/router_model.rb
         | 
| 1034 1034 | 
             
            - lib/openc3/models/router_status_model.rb
         | 
| 1035 1035 | 
             
            - lib/openc3/models/scope_model.rb
         | 
| 1036 | 
            +
            - lib/openc3/models/script_status_model.rb
         | 
| 1036 1037 | 
             
            - lib/openc3/models/secret_model.rb
         | 
| 1037 1038 | 
             
            - lib/openc3/models/setting_model.rb
         | 
| 1038 1039 | 
             
            - lib/openc3/models/sorted_model.rb
         | 
| @@ -1284,7 +1285,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 1284 1285 | 
             
                - !ruby/object:Gem::Version
         | 
| 1285 1286 | 
             
                  version: '0'
         | 
| 1286 1287 | 
             
            requirements: []
         | 
| 1287 | 
            -
            rubygems_version: 3.6. | 
| 1288 | 
            +
            rubygems_version: 3.6.8
         | 
| 1288 1289 | 
             
            specification_version: 4
         | 
| 1289 1290 | 
             
            summary: OpenC3
         | 
| 1290 1291 | 
             
            test_files: []
         |