openc3 6.0.2 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/bin/pipinstall +1 -1
  3. data/bin/pipuninstall +10 -0
  4. data/data/config/parameter_modifiers.yaml +11 -0
  5. data/data/config/widgets.yaml +38 -1
  6. data/lib/openc3/api/settings_api.rb +35 -2
  7. data/lib/openc3/api/tlm_api.rb +11 -2
  8. data/lib/openc3/config/config_parser.rb +4 -2
  9. data/lib/openc3/io/json_api_object.rb +4 -4
  10. data/lib/openc3/microservices/interface_decom_common.rb +4 -0
  11. data/lib/openc3/microservices/periodic_microservice.rb +26 -1
  12. data/lib/openc3/migrations/20250108060000_news_feed.rb +15 -0
  13. data/lib/openc3/models/activity_model.rb +11 -4
  14. data/lib/openc3/models/news_model.rb +38 -0
  15. data/lib/openc3/models/python_package_model.rb +2 -1
  16. data/lib/openc3/models/scope_model.rb +3 -1
  17. data/lib/openc3/models/timeline_model.rb +3 -6
  18. data/lib/openc3/packets/commands.rb +2 -1
  19. data/lib/openc3/packets/packet.rb +1 -1
  20. data/lib/openc3/script/calendar.rb +21 -18
  21. data/lib/openc3/script/plugins.rb +10 -2
  22. data/lib/openc3/script/script.rb +11 -6
  23. data/lib/openc3/script/script_runner.rb +22 -11
  24. data/lib/openc3/script/storage.rb +3 -3
  25. data/lib/openc3/script/tables.rb +49 -0
  26. data/lib/openc3/script/web_socket_api.rb +2 -2
  27. data/lib/openc3/utilities/local_mode.rb +13 -2
  28. data/lib/openc3/utilities/target_file.rb +10 -2
  29. data/lib/openc3/version.rb +6 -6
  30. data/templates/tool_angular/package.json +2 -2
  31. data/templates/tool_react/package.json +1 -1
  32. data/templates/tool_svelte/package.json +1 -1
  33. data/templates/tool_vue/package.json +3 -3
  34. data/templates/widget/package.json +2 -2
  35. metadata +6 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e7cb703367de9f45fc0ffb6d7fd9e2d4fbf075d3c2be95081468558a757df2f6
4
- data.tar.gz: 2580ce86c27ac4dc6a8dcdaa2e8c8531de1ff3ad5fb803516b70e4c8ac74c90f
3
+ metadata.gz: be3485e674a1fe4a236e3a71b57aff4d7d7398a0638a21d8069994739a53edab
4
+ data.tar.gz: 911ee7331cc9100dc1dfbb7abe3e7f38baaa5b201be2974d882f0ef01701e9da
5
5
  SHA512:
6
- metadata.gz: 43cb5951f41970eed0b27f355894d5c014b3f69132abe381640e6d9c1d9d04ab7eab8aecaf14c8e7ec1a51353ec953c68ee4e1ba7783f6d58014ae545dde48cf
7
- data.tar.gz: a146e2222479e99a6afe1b726bc0e5c08863db658203538d3781ba83d06e3944f74e41e85b49572f52ddd59d56b55428b937e4ab3a6902277a4dd34df2746e22
6
+ metadata.gz: eafafaac4ae90e0bb594446fefdfc7b9e6eb96be4b08c5798d827d60a2377fad9252bca9d2e4495b8d4073283aa957d89fab0214d998bd9c4bbbb5d9174000cc
7
+ data.tar.gz: ea90586ba1347a74964c3cbb181810463caf507bb22c88bed8253b2fc9164b7f1648300d35163aba95f9e96f29f31ccb6f4406063dfefa7d33fd8057acddc416
data/bin/pipinstall CHANGED
@@ -9,6 +9,6 @@ else
9
9
  echo "Command failed - retrying with --no-index"
10
10
  pip3 install --no-index "$@"
11
11
  if [ $? -eq 0 ]; then
12
- echo "ERROR: pip install failed"
12
+ echo "ERROR: pip3 install failed"
13
13
  fi
14
14
  fi
data/bin/pipuninstall ADDED
@@ -0,0 +1,10 @@
1
+ #!/bin/sh
2
+ python3 -m venv $PYTHONUSERBASE
3
+ source $PYTHONUSERBASE/bin/activate
4
+ echo "pip3 uninstall $@"
5
+ pip3 uninstall "$@"
6
+ if [ $? -eq 0 ]; then
7
+ echo "Command succeeded"
8
+ else
9
+ echo "ERROR: pip3 uninstall failed"
10
+ fi
@@ -71,6 +71,17 @@ WRITE_CONVERSION:
71
71
  factor is applied to the value entered by the user before it is written into
72
72
  the binary command packet and sent.
73
73
 
74
+ When applying a write_conversion sometimes the data type changes,
75
+ e.g. creating a UINT from an input STRING (for an example of this see
76
+ [ip_write_conversion.rb](https://github.com/OpenC3/cosmos/blob/main/openc3/lib/openc3/conversions/ip_write_conversion.rb)
77
+ or [ip_write_conversion.py](https://github.com/OpenC3/cosmos/blob/main/openc3/python/openc3/conversions/ip_write_conversion.py)).
78
+ In this case, the command definition data type is UINT and the min, max values don't matter
79
+ (but must be given) so are typically set to MIN MAX. The default value is important
80
+ and should be specified as a string. For a full example see the IP_ADDRESS parameter
81
+ in the TIME_OFFSET command definition of the COSMOS Demo
82
+ [INST inst_cmds.txt](https://github.com/OpenC3/cosmos/blob/main/openc3-cosmos-init/plugins/packages/openc3-cosmos-demo/targets/INST/cmd_tlm/inst_cmds.txt)
83
+ or [INST2 inst_cmds.txt](https://github.com/OpenC3/cosmos/blob/main/openc3-cosmos-init/plugins/packages/openc3-cosmos-demo/targets/INST2/cmd_tlm/inst_cmds.txt).
84
+
74
85
  :::info Multiple write conversions on command parameters
75
86
  When a command is built, each item gets written (and write conversions are run)
76
87
  to set the default value. Then items are written (again write conversions are run)
@@ -245,6 +245,40 @@ Telemetry Widgets:
245
245
  example: |
246
246
  ARRAY INST HEALTH_STATUS ARY 250 80 "0x%x" 6 FORMATTED
247
247
  ARRAY INST HEALTH_STATUS ARY2 200 100 nil 4 WITH_UNITS
248
+ ARRAYPLOT:
249
+ summary: Plot an array of values.
250
+ description:
251
+ The item can either be a simple array or a 2D array of x values and y values, e.g. [[x1, x2, x3], [y1, y2, y3]].
252
+ If the X_AXIS setting is not specified, the X axis starts with 0 and increments by 1.
253
+ If the X_AXIS setting is used the x values of a 2D array will be ignored.
254
+ settings:
255
+ TITLE:
256
+ summary: Title of the plot
257
+ parameters:
258
+ - name: Title
259
+ required: true
260
+ description: Title of the plot
261
+ values: .+
262
+ X_AXIS:
263
+ summary: Define the x-axis parameters for the plot
264
+ parameters:
265
+ - name: Start
266
+ required: true
267
+ description: Start value for the x-axis
268
+ values: .+
269
+ - name: Step
270
+ required: true
271
+ description: Step value for the x-axis
272
+ values: .+
273
+ # Inject the graph settings
274
+ <%= MetaConfigParser.load('graph_settings.yaml').to_meta_config_yaml(8) %>
275
+ example: |
276
+ ARRAYPLOT
277
+ SETTING TITLE "Array Data"
278
+ SETTING ITEM INST HEALTH_STATUS ARY
279
+ SETTING ITEM INST HEALTH_STATUS ARY2
280
+ SETTING SIZE 600 400
281
+ SETTING X_AXIS 10 10
248
282
  BLOCK:
249
283
  summary: Displays BLOCK data organized into rows and space separated
250
284
  parameters:
@@ -372,6 +406,7 @@ Telemetry Widgets:
372
406
  FORMATVALUE INST LATEST TEMP1 %.2f CONVERTED 20
373
407
  LABELLED:
374
408
  summary: Displays a LABEL followed by a LED
409
+ description: See the LED widget for more information
375
410
  parameters:
376
411
  - name: Target name
377
412
  required: true
@@ -688,6 +723,7 @@ Telemetry Widgets:
688
723
  Additional values can be added by using the LED_COLOR setting. For example
689
724
  LED INST PARAMS VALUE3 RAW can be followed by SETTING LED_COLOR 0 GREEN,
690
725
  SETTING LED_COLOR 1 RED, and SETTING LED_COLOR ANY ORANGE.
726
+ See LIMITSCOLOR for a widget that displays a circle depicting the limits color of an item.
691
727
  parameters:
692
728
  - name: Target name
693
729
  required: true
@@ -791,7 +827,8 @@ Telemetry Widgets:
791
827
  LIMITSCOLUMN INST HEALTH_STATUS TEMP1 CONVERTED 50 200
792
828
  LIMITSCOLUMN INST HEALTH_STATUS TEMP1
793
829
  LIMITSCOLOR:
794
- summary: Displays a circle depicting the limits color of an item
830
+ summary: Displays a circle depicting the limits color of an item.
831
+ See LED for a widget that displays a circle which changes to an arbitrary color based on telemetry values.
795
832
  parameters:
796
833
  - name: Target name
797
834
  required: true
@@ -14,13 +14,23 @@
14
14
  # GNU Affero General Public License for more details.
15
15
 
16
16
  # Modified by OpenC3, Inc.
17
- # All changes Copyright 2022, OpenC3, Inc.
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
+ begin
24
+ require 'openc3-enterprise/version'
25
+ VERSION = OPENC3_ENTERPRISE_VERSION
26
+ ENTERPRISE = true
27
+ rescue LoadError
28
+ require 'openc3/version'
29
+ VERSION = OPENC3_VERSION
30
+ ENTERPRISE = false
31
+ end
23
32
  require 'openc3/models/setting_model'
33
+ require 'openc3/models/news_model'
24
34
 
25
35
  module OpenC3
26
36
  module Api
@@ -31,7 +41,8 @@ module OpenC3
31
41
  'get_setting',
32
42
  'get_settings',
33
43
  'set_setting',
34
- 'save_setting' # DEPRECATED
44
+ 'save_setting', # DEPRECATED
45
+ 'update_news',
35
46
  ])
36
47
 
37
48
  def list_settings(manual: false, scope: $openc3_scope, token: $openc3_token)
@@ -68,5 +79,27 @@ module OpenC3
68
79
  end
69
80
  # save_setting is DEPRECATED
70
81
  alias save_setting set_setting
82
+
83
+ # Update the news feed on demand to respond to frontend setting changes
84
+ def update_news(manual: false, scope: $openc3_scope, token: $openc3_token)
85
+ authorize(permission: 'admin', manual: manual, scope: scope, token: token)
86
+ conn = Faraday.new(
87
+ url: 'https://news.openc3.com',
88
+ params: {version: VERSION, enterprise: ENTERPRISE},
89
+ )
90
+ response = conn.get('/news')
91
+ if response.success?
92
+ NewsModel.set(response.body)
93
+ else
94
+ NewsModel.news_error(response)
95
+ end
96
+
97
+ # Test code to update the news feed with a dummy message
98
+ # data = NewsModel.all()
99
+ # json = JSON.parse(data)
100
+ # json.unshift( { date: Time.now.utc.iso8601, title: "News at #{Time.now}", body: "The news feed has been updated at #{Time.now}." })
101
+ # json.pop if json.length > 5
102
+ # NewsModel.set(json.to_json)
103
+ end
71
104
  end
72
105
  end
@@ -35,7 +35,7 @@ module OpenC3
35
35
  'tlm_raw',
36
36
  'tlm_formatted',
37
37
  'tlm_with_units',
38
- 'tlm_variable',
38
+ 'tlm_variable', # DEPRECATED
39
39
  'set_tlm',
40
40
  'inject_tlm',
41
41
  'override_tlm',
@@ -131,7 +131,16 @@ module OpenC3
131
131
  if item_hash
132
132
  item_hash = item_hash.transform_keys(&:upcase)
133
133
  # Check that the items exist ... exceptions are raised if not
134
- TargetModel.packet_items(target_name, packet_name, item_hash.keys, scope: scope)
134
+ items = TargetModel.packet_items(target_name, packet_name, item_hash.keys, scope: scope)
135
+ if type == :CONVERTED
136
+ # If the type is converted, check that the item states are valid
137
+ item_hash.each do |item_name, item_value|
138
+ item = items.find { |i| i['name'] == item_name.to_s.upcase }
139
+ if item['states'] && !item['states'][item_value]
140
+ raise "Unknown state '#{item_value}' for #{item['name']}, must be one of #{item['states'].keys.join(', ')}"
141
+ end
142
+ end
143
+ end
135
144
  else
136
145
  # Check that the packet exists ... exceptions are raised if not
137
146
  TargetModel.packet(target_name, packet_name, scope: scope)
@@ -380,9 +380,11 @@ module OpenC3
380
380
  return Float::INFINITY
381
381
  when 'NEG_INFINITY'
382
382
  return -Float::INFINITY
383
- else
384
- raise ArgumentError, "Could not convert constant: #{value}"
385
383
  end
384
+ # NOTE: No else case because of the following scenario:
385
+ # If the value type is a UINT but they have a WRITE_CONVERSION that takes a string
386
+ # then the default value will be a string. In that case we just want to return the string.
387
+ # For example, the IP_ADDRESS parameter in the TIME_OFFSET command in the Demo plugin.
386
388
  end
387
389
  return value
388
390
  end
@@ -67,6 +67,7 @@ module OpenC3
67
67
  @authentication = authentication.nil? ? generate_auth() : authentication
68
68
  @timeout = timeout
69
69
  @shutdown = false
70
+ # JsonDRb.debug = true # Enable for debugging
70
71
  end
71
72
 
72
73
  # generate the auth object
@@ -179,12 +180,11 @@ module OpenC3
179
180
  # NOTE: This is a helper method and should not be called directly
180
181
  def _generate_data(kwargs)
181
182
  data = kwargs[:data]
182
- if data.nil?
183
- data = kwargs[:data] = {}
184
- elsif data.is_a?(Hash) == false and data.is_a?(String) == false
183
+ # data can be nil but otherwise must be a Hash or String
184
+ if !data.nil? and !data.is_a?(Hash) and !data.is_a?(String)
185
185
  raise JsonApiError, "incorrect type for keyword 'data' MUST be Hash or String: #{data}"
186
186
  end
187
- return kwargs[:json] ? JSON.generate(kwargs[:data]) : kwargs[:data]
187
+ return kwargs[:json] ? JSON.generate(data) : data
188
188
  end
189
189
 
190
190
  # NOTE: This is a helper method and should not be called directly
@@ -36,6 +36,10 @@ module OpenC3
36
36
  packet.received_count += 1
37
37
  packet.received_time = Time.now.sys
38
38
  TelemetryTopic.write_packet(packet, scope: @scope)
39
+ # If the inject_tlm parameters are bad we rescue so
40
+ # interface_microservice and decom_microservice can continue
41
+ rescue => e
42
+ @logger.error "inject_tlm error due to #{e.message}"
39
43
  end
40
44
 
41
45
  def handle_build_cmd(build_cmd_json, msg_id)
@@ -1,6 +1,6 @@
1
1
  # encoding: ascii-8bit
2
2
 
3
- # Copyright 2022 OpenC3, Inc.
3
+ # Copyright 2025 OpenC3, Inc.
4
4
  # All Rights Reserved.
5
5
  #
6
6
  # This program is free software; you can modify and/or redistribute it
@@ -18,15 +18,39 @@
18
18
 
19
19
  require 'openc3/microservices/microservice'
20
20
  require 'openc3/models/offline_access_model'
21
+ require 'openc3/models/news_model'
22
+ # The VERSION and ENTERPRISE constants are set by settings_api.rb
23
+ require 'openc3/api/settings_api'
21
24
 
22
25
  module OpenC3
23
26
  class PeriodicMicroservice < Microservice
27
+ include Api
28
+
24
29
  STARTUP_DELAY_SECONDS = 2 * 60 # Two Minutes
25
30
  SLEEP_PERIOD_SECONDS = 24 * 60 * 60 # Run once per day
26
31
 
27
32
  def initialize(*args)
28
33
  super(*args)
29
34
  @metric.set(name: 'periodic_total', value: @count, type: 'counter')
35
+ @conn = nil # Faraday connection set by get_news
36
+ get_news()
37
+ end
38
+
39
+ def get_news
40
+ if get_setting('news_feed', scope: @scope)
41
+ unless @conn
42
+ @conn = Faraday.new(
43
+ url: 'https://news.openc3.com',
44
+ params: {version: VERSION, enterprise: ENTERPRISE},
45
+ )
46
+ end
47
+ response = @conn.get('/news')
48
+ if response.success?
49
+ NewsModel.set(response.body)
50
+ else
51
+ NewsModel.news_error(response)
52
+ end
53
+ end
30
54
  end
31
55
 
32
56
  def run
@@ -52,6 +76,7 @@ module OpenC3
52
76
  @metric.set(name: 'periodic_total', value: @count, type: 'counter')
53
77
  break if @cancel_thread
54
78
  break if @run_sleeper.sleep(SLEEP_PERIOD_SECONDS)
79
+ get_news()
55
80
  end
56
81
  end
57
82
 
@@ -0,0 +1,15 @@
1
+ require 'openc3/utilities/migration'
2
+ require 'openc3/models/scope_model'
3
+ require 'openc3/models/setting_model'
4
+
5
+ module OpenC3
6
+ class NewsFeed < Migration
7
+ def self.run
8
+ SettingModel.set({ name: 'news_feed', data: true }, scope: 'DEFAULT')
9
+ end
10
+ end
11
+ end
12
+
13
+ unless ENV['OPENC3_NO_MIGRATE']
14
+ OpenC3::NewsFeed.run
15
+ end
@@ -66,10 +66,17 @@ module OpenC3
66
66
  end
67
67
 
68
68
  # @return [String|nil] String of the saved json or nil if score not found under primary_key
69
- def self.score(name:, score:, scope:)
70
- value = Store.zrangebyscore("#{scope}#{PRIMARY_KEY}__#{name}", score, score, :limit => [0, 1]).first
71
- if value
72
- return ActivityModel.from_json(value, name: name, scope: scope)
69
+ def self.score(name:, score:, scope:, uuid: nil)
70
+ values = Store.zrangebyscore("#{scope}#{PRIMARY_KEY}__#{name}", score, score)
71
+ if values and values.length > 0
72
+ if uuid
73
+ values.each do |value|
74
+ activity = ActivityModel.from_json(value, name: name, scope: scope)
75
+ return activity if activity.uuid == uuid
76
+ end
77
+ else
78
+ return ActivityModel.from_json(values[0], name: name, scope: scope)
79
+ end
73
80
  end
74
81
  return nil
75
82
  end
@@ -0,0 +1,38 @@
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
+ require 'openc3/utilities/store'
21
+
22
+ module OpenC3
23
+ class NewsModel < Model
24
+ PRIMARY_KEY = 'openc3_news'
25
+
26
+ def self.set(news)
27
+ Store.set(PRIMARY_KEY, news)
28
+ end
29
+
30
+ def self.all()
31
+ Store.get(PRIMARY_KEY)
32
+ end
33
+
34
+ def self.news_error(response)
35
+ Store.set(PRIMARY_KEY, [{ date: Time.now.utc.iso8601, title: 'News Error', body: "Error contacting OpenC3 news feed (status: #{response.status})" }].to_json)
36
+ end
37
+ end
38
+ end
@@ -104,7 +104,8 @@ module OpenC3
104
104
  def self.destroy(name, scope:)
105
105
  package_name, version = self.extract_name_and_version(name)
106
106
  Logger.info "Uninstalling package: #{name}"
107
- result = OpenC3::ProcessManager.instance.spawn(["pip", "uninstall", package_name, "-y"], "package_uninstall", name, Time.now + 3600.0, scope: scope)
107
+ pip_args = ["-y", package_name]
108
+ result = OpenC3::ProcessManager.instance.spawn(["/openc3/bin/pipuninstall"] + pip_args, "package_uninstall", name, Time.now + 3600.0, scope: scope)
108
109
  return result.name
109
110
  end
110
111
 
@@ -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 2024, OpenC3, Inc.
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
@@ -400,6 +400,8 @@ module OpenC3
400
400
  SettingModel.set({ name: 'rubygems_url', data: ENV['RUBYGEMS_URL'] || 'https://rubygems.org' }, scope: @scope) unless setting
401
401
  setting = SettingModel.get(name: 'pypi_url')
402
402
  SettingModel.set({ name: 'pypi_url', data: ENV['PYPI_URL'] || 'https://pypi.org' }, scope: @scope) unless setting
403
+ # Set the news feed to true by default, don't bother checking if it's already set
404
+ SettingModel.set({ name: 'news_feed', data: true }, scope: @scope)
403
405
  end
404
406
  end
405
407
  end
@@ -94,13 +94,10 @@ module OpenC3
94
94
  if color.nil?
95
95
  color = '#%06x' % (rand * 0xffffff)
96
96
  end
97
- valid_color = color =~ /[0-9a-fA-F]{6}/
98
- if valid_color.nil?
99
- raise RuntimeError.new "invalid color but in hex format. #FF0000"
100
- end
101
- unless color.start_with?('#')
102
- color = "##{color}"
97
+ unless color =~ /#?([0-9a-fA-F]{6})/
98
+ raise TimelineInputError.new "invalid color, must be in hex format, e.g. #FF0000"
103
99
  end
100
+ color = "##{color}" unless color.start_with?('#')
104
101
  @color = color
105
102
  end
106
103
 
@@ -326,7 +326,8 @@ module OpenC3
326
326
  end
327
327
 
328
328
  range = item.range
329
- if range
329
+ # Don't range check a string default value
330
+ if range and !item.default.is_a?(String)
330
331
  # Perform Range Check on command parameter
331
332
  if not range.include?(range_check_value)
332
333
  range_check_value = "'#{range_check_value}'" if String === range_check_value
@@ -756,7 +756,7 @@ module OpenC3
756
756
  super(item, value, :RAW, buffer)
757
757
  rescue ArgumentError => e
758
758
  if item.states and String === value and e.message =~ /invalid value for/
759
- raise "Unknown state #{value} for #{item.name}, must be one of #{item.states.keys.join(', ')}"
759
+ raise "Unknown state '#{value}' for #{item.name}, must be one of #{item.states.keys.join(', ')}"
760
760
  else
761
761
  raise e
762
762
  end
@@ -23,12 +23,12 @@ module OpenC3
23
23
 
24
24
  private
25
25
 
26
- def list_timelines(scope: $openc3_scope, token: $openc3_token)
26
+ def list_timelines(scope: $openc3_scope)
27
27
  response = $api_server.request('get', "/openc3-api/timeline", scope: scope)
28
28
  return _handle_response(response, 'Failed to list timelines')
29
29
  end
30
30
 
31
- def create_timeline(name, color: nil, scope: $openc3_scope, token: $openc3_token)
31
+ def create_timeline(name, color: nil, scope: $openc3_scope)
32
32
  data = {}
33
33
  data['name'] = name
34
34
  data['color'] = color if color
@@ -36,19 +36,19 @@ module OpenC3
36
36
  return _handle_response(response, 'Failed to create timeline')
37
37
  end
38
38
 
39
- def get_timeline(name, scope: $openc3_scope, token: $openc3_token)
39
+ def get_timeline(name, scope: $openc3_scope)
40
40
  response = $api_server.request('get', "/openc3-api/timeline/#{name}", scope: scope)
41
41
  return _handle_response(response, 'Failed to get timeline')
42
42
  end
43
43
 
44
- def set_timeline_color(name, color, scope: $openc3_scope, token: $openc3_token)
44
+ def set_timeline_color(name, color, scope: $openc3_scope)
45
45
  post_data = {}
46
46
  post_data['color'] = color
47
47
  response = $api_server.request('post', "/openc3-api/timeline/#{name}/color", data: post_data, json: true, scope: scope)
48
48
  return _handle_response(response, 'Failed to set timeline color')
49
49
  end
50
50
 
51
- def delete_timeline(name, force: false, scope: $openc3_scope, token: $openc3_token)
51
+ def delete_timeline(name, force: false, scope: $openc3_scope)
52
52
  url = "/openc3-api/timeline/#{name}"
53
53
  if force
54
54
  url += "?force=true"
@@ -57,16 +57,7 @@ module OpenC3
57
57
  return _handle_response(response, 'Failed to delete timeline')
58
58
  end
59
59
 
60
- def get_timeline_activities(name, start: nil, stop: nil, scope: $openc3_scope, token: $openc3_token)
61
- url = "/openc3-api/timeline/#{name}/activities"
62
- if start and stop
63
- url += "?start=#{start}&stop=#{stop}"
64
- end
65
- response = $api_server.request('get', url, scope: scope)
66
- return _handle_response(response, 'Failed to get timeline activities')
67
- end
68
-
69
- def create_timeline_activity(name, kind:, start:, stop:, data: {}, scope: $openc3_scope, token: $openc3_token)
60
+ def create_timeline_activity(name, kind:, start:, stop:, data: {}, scope: $openc3_scope)
70
61
  kind = kind.to_s.downcase()
71
62
  kinds = %w(command script reserve)
72
63
  unless kinds.include?(kind)
@@ -81,12 +72,24 @@ module OpenC3
81
72
  return _handle_response(response, 'Failed to create timeline activity')
82
73
  end
83
74
 
84
- def get_timeline_activity(name, start: nil, scope: $openc3_scope, token: $openc3_token)
85
- response = $api_server.request('get', "/openc3-api/timeline/#{name}/activity/#{start}", scope: scope)
75
+ def get_timeline_activity(name, start, uuid, scope: $openc3_scope)
76
+ response = $api_server.request('get', "/openc3-api/timeline/#{name}/activity/#{start}/#{uuid}", scope: scope)
86
77
  return _handle_response(response, 'Failed to get timeline activity')
87
78
  end
88
79
 
89
- def delete_timeline_activity(name, start, uuid, scope: $openc3_scope, token: $openc3_token)
80
+ def get_timeline_activities(name, start: nil, stop: nil, limit: nil, scope: $openc3_scope)
81
+ url = "/openc3-api/timeline/#{name}/activities"
82
+ if start and stop
83
+ url += "?start=#{start}&stop=#{stop}"
84
+ end
85
+ if limit
86
+ url += "?limit=#{limit}"
87
+ end
88
+ response = $api_server.request('get', url, scope: scope)
89
+ return _handle_response(response, 'Failed to get timeline activities')
90
+ end
91
+
92
+ def delete_timeline_activity(name, start, uuid, scope: $openc3_scope)
90
93
  response = $api_server.request('delete', "/openc3-api/timeline/#{name}/activity/#{start}/#{uuid}", scope: scope)
91
94
  return _handle_response(response, 'Failed to delete timeline activity')
92
95
  end
@@ -20,7 +20,7 @@ module OpenC3
20
20
  module Script
21
21
  private
22
22
 
23
- def plugin_list(scope: $openc3_scope)
23
+ def plugin_list(default: false, scope: $openc3_scope)
24
24
  response_body = nil
25
25
  begin
26
26
  endpoint = "/openc3-api/plugins?scope=#{scope}"
@@ -35,7 +35,15 @@ module OpenC3
35
35
  http.request(request) do |response|
36
36
  response_body = response.body
37
37
  response.value() # Raises an HTTP error if the response is not 2xx (success)
38
- return JSON.parse(response.body, allow_nan: true, create_additions: true)
38
+ plugins = JSON.parse(response.body, allow_nan: true, create_additions: true)
39
+ if default
40
+ return plugins
41
+ else
42
+ return plugins.select do |plugin|
43
+ !plugin.include?('openc3-cosmos-tool-') and !plugin.include?('openc3-tool-base') and
44
+ !plugin.include?('openc3-cosmos-enterprise-tool-') and !plugin.include?('openc3-enterprise-tool-base')
45
+ end
46
+ end
39
47
  end
40
48
  end
41
49
  rescue => e
@@ -25,18 +25,23 @@ require 'openc3/api/api'
25
25
  require 'openc3/io/json_drb_object'
26
26
  require 'openc3/script/api_shared'
27
27
  require 'openc3/script/calendar'
28
- require 'openc3/script/metadata'
29
28
  require 'openc3/script/commands'
30
- require 'openc3/script/telemetry'
31
- require 'openc3/script/limits'
29
+ require 'openc3/script/critical_cmd'
32
30
  require 'openc3/script/exceptions'
31
+ # openc3/script/extract is just helper methods
32
+ require 'openc3/script/limits'
33
+ require 'openc3/script/metadata'
34
+ require 'openc3/script/packages'
35
+ require 'openc3/script/plugins'
33
36
  require 'openc3/script/screen'
34
37
  require 'openc3/script/script_runner'
35
38
  require 'openc3/script/storage'
39
+ # openc3/script/suite_results and suite_runner are used by
40
+ # running_script.rb and the script_runner_api
41
+ # openc3/script/suite is used by end user SR Suites
42
+ require 'openc3/script/tables'
43
+ require 'openc3/script/telemetry'
36
44
  require 'openc3/script/web_socket_api'
37
- require 'openc3/script/packages'
38
- require 'openc3/script/plugins'
39
- require 'openc3/script/critical_cmd'
40
45
  require 'openc3/utilities/authentication'
41
46
 
42
47
  $api_server = nil
@@ -34,22 +34,29 @@ module OpenC3
34
34
  if response.nil? || response.status != 200
35
35
  _script_response_error(response, "Script list request failed", scope: scope)
36
36
  else
37
- return JSON.parse(response.body, :allow_nan => true, :create_additions => true)
37
+ scripts = JSON.parse(response.body, :allow_nan => true, :create_additions => true)
38
+ # Remove the '*' from the script names
39
+ return scripts.each { |script| script.gsub!(/\*$/, '') }
38
40
  end
39
41
  end
40
42
 
41
43
  def script_syntax_check(script, scope: $openc3_scope)
42
- endpoint = "/script-api/scripts/syntax"
43
- response = $script_runner_api_server.request('post', endpoint, json: true, data: script, scope: scope)
44
+ endpoint = "/script-api/scripts/temp.rb/syntax"
45
+ # Explicitly set the headers to plain/text so the request.body is set correctly
46
+ headers = {
47
+ 'Content-Type': 'plain/text',
48
+ }
49
+ response = $script_runner_api_server.request('post', endpoint, headers: headers, data: script, scope: scope)
44
50
  if response.nil? || response.status != 200
45
51
  _script_response_error(response, "Script syntax check request failed", scope: scope)
46
52
  else
47
53
  result = JSON.parse(response.body, :allow_nan => true, :create_additions => true)
48
54
  if result['title'] == "Syntax Check Successful"
49
- return true
55
+ result['success'] = true
50
56
  else
51
- raise result.inspect
57
+ result['success'] = false
52
58
  end
59
+ return result
53
60
  end
54
61
  end
55
62
 
@@ -59,8 +66,8 @@ module OpenC3
59
66
  if response.nil? || response.status != 200
60
67
  _script_response_error(response, "Failed to get #{filename}", scope: scope)
61
68
  else
62
- script = response.body
63
- return script
69
+ result = JSON.parse(response.body, :allow_nan => true, :create_additions => true)
70
+ return result['contents']
64
71
  end
65
72
  end
66
73
 
@@ -120,9 +127,13 @@ module OpenC3
120
127
  end
121
128
  end
122
129
 
123
- def script_instrumented(filename, script, scope: $openc3_scope)
124
- endpoint = "/script-api/scripts/#{filename}/instrumented"
125
- response = $script_runner_api_server.request('post', endpoint, json: true, data: script, scope: scope)
130
+ def script_instrumented(script, scope: $openc3_scope)
131
+ endpoint = "/script-api/scripts/temp.rb/instrumented"
132
+ # Explicitly set the headers to plain/text so the request.body is set correctly
133
+ headers = {
134
+ 'Content-Type': 'plain/text',
135
+ }
136
+ response = $script_runner_api_server.request('post', endpoint, headers: headers, data: script, scope: scope)
126
137
  if response.nil? || response.status != 200
127
138
  _script_response_error(response, "Script instrumented request failed", scope: scope)
128
139
  else
@@ -178,7 +189,7 @@ module OpenC3
178
189
 
179
190
  def _running_script_action(id, action_name, scope: $openc3_scope)
180
191
  endpoint = "/script-api/running-script/#{id}/#{action_name}"
181
- response = $script_runner_api_server.request('post', endpoint, scope: scope)
192
+ response = $script_runner_api_server.request('post', endpoint, json: true, scope: scope)
182
193
  if response.nil? || response.status != 200
183
194
  _script_response_error(response, "Running script #{action_name} request failed", scope: scope)
184
195
  else
@@ -127,7 +127,9 @@ module OpenC3
127
127
  end
128
128
  end
129
129
 
130
- def get_download_url(path, scope: $openc3_scope)
130
+ # These are helper methods ... should not be used directly
131
+
132
+ def _get_download_url(path, scope: $openc3_scope)
131
133
  targets = "targets_modified" # First try targets_modified
132
134
  response = $api_server.request('get', "/openc3-api/storage/exists/#{scope}/#{targets}/#{path}", query: { bucket: 'OPENC3_CONFIG_BUCKET' }, scope: scope)
133
135
  if response.status != 200
@@ -143,8 +145,6 @@ module OpenC3
143
145
  return result['url']
144
146
  end
145
147
 
146
- # These are helper methods ... should not be used directly
147
-
148
148
  def _get_storage_file(path, scope: $openc3_scope)
149
149
  # Create Tempfile to store data
150
150
  file = Tempfile.new('target', binmode: true)
@@ -0,0 +1,49 @@
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
+ module OpenC3
20
+ module Script
21
+ private
22
+
23
+ def table_create_binary(definition, scope: $openc3_scope)
24
+ post_data = {}
25
+ post_data['definition'] = definition
26
+ response = $api_server.request('post', '/openc3-api/tables/generate', json: true, data: post_data, scope: scope)
27
+ return _handle_response(response, 'Failed to create binary')
28
+ end
29
+
30
+ def table_create_report(filename, definition, table_name: nil, scope: $openc3_scope)
31
+ post_data = {}
32
+ post_data['binary'] = filename
33
+ post_data['definition'] = definition
34
+ post_data['table_name'] = table_name if table_name
35
+ response = $api_server.request('post', '/openc3-api/tables/report', json: true, data: post_data, scope: scope)
36
+ return _handle_response(response, 'Failed to create report')
37
+ end
38
+
39
+ # Helper method to handle the response
40
+ def _handle_response(response, error_message)
41
+ return nil if response.nil?
42
+ if response.status >= 400
43
+ result = JSON.parse(response.body, :allow_nan => true, :create_additions => true)
44
+ raise "#{error_message} due to #{result['message']}"
45
+ end
46
+ return JSON.parse(response.body, :allow_nan => true, :create_additions => true)
47
+ end
48
+ end
49
+ end
@@ -182,7 +182,7 @@ module OpenC3
182
182
  def generate_url
183
183
  schema = ENV['OPENC3_API_SCHEMA'] || 'http'
184
184
  hostname = ENV['OPENC3_API_HOSTNAME'] || (ENV['OPENC3_DEVEL'] ? '127.0.0.1' : 'openc3-cosmos-cmd-tlm-api')
185
- port = ENV['OPENC3_API_PORT'] || '2901'
185
+ port = ENV['OPENC3_API_CABLE_PORT'] || ENV['OPENC3_API_PORT'] || '3901'
186
186
  port = port.to_i
187
187
  return "#{schema}://#{hostname}:#{port}/openc3-api/cable"
188
188
  end
@@ -198,7 +198,7 @@ module OpenC3
198
198
  def generate_url
199
199
  schema = ENV['OPENC3_SCRIPT_API_SCHEMA'] || 'http'
200
200
  hostname = ENV['OPENC3_SCRIPT_API_HOSTNAME'] || (ENV['OPENC3_DEVEL'] ? '127.0.0.1' : 'openc3-cosmos-script-runner-api')
201
- port = ENV['OPENC3_SCRIPT_API_PORT'] || '2902'
201
+ port = ENV['OPENC3_SCRIPT_API_CABLE_PORT'] || ENV['OPENC3_SCRIPT_API_PORT'] || '3902'
202
202
  port = port.to_i
203
203
  return "#{schema}://#{hostname}:#{port}/script-api/cable"
204
204
  end
@@ -380,6 +380,7 @@ module OpenC3
380
380
 
381
381
  def self.put_target_file(path, io_or_string, scope:)
382
382
  full_folder_path = "#{OPENC3_LOCAL_MODE_PATH}/#{path}"
383
+ return unless File.expand_path(full_folder_path).start_with?(OPENC3_LOCAL_MODE_PATH)
383
384
  FileUtils.mkdir_p(File.dirname(full_folder_path))
384
385
  File.open(full_folder_path, 'wb') do |file|
385
386
  if String === io_or_string
@@ -393,7 +394,10 @@ module OpenC3
393
394
 
394
395
  def self.open_local_file(path, scope:)
395
396
  full_path = "#{OPENC3_LOCAL_MODE_PATH}/#{scope}/targets_modified/#{path}"
396
- return File.open(full_path, 'rb')
397
+ if File.expand_path(full_path).start_with?(OPENC3_LOCAL_MODE_PATH)
398
+ return File.open(full_path, 'rb')
399
+ end
400
+ nil
397
401
  rescue Errno::ENOENT
398
402
  nil
399
403
  end
@@ -446,6 +450,7 @@ module OpenC3
446
450
  def self.save_tool_config(scope, tool, name, data)
447
451
  json = JSON.parse(data, :allow_nan => true, :create_additions => true)
448
452
  config_path = "#{OPENC3_LOCAL_MODE_PATH}/#{scope}/tool_config/#{tool}/#{name}.json"
453
+ return unless File.expand_path(config_path).start_with?(OPENC3_LOCAL_MODE_PATH)
449
454
  FileUtils.mkdir_p(File.dirname(config_path))
450
455
  File.open(config_path, 'w') do |file|
451
456
  file.write(JSON.pretty_generate(json, :allow_nan => true))
@@ -453,7 +458,9 @@ module OpenC3
453
458
  end
454
459
 
455
460
  def self.delete_tool_config(scope, tool, name)
456
- FileUtils.rm_f("#{OPENC3_LOCAL_MODE_PATH}/#{scope}/tool_config/#{tool}/#{name}.json")
461
+ config_path = "#{OPENC3_LOCAL_MODE_PATH}/#{scope}/tool_config/#{tool}/#{name}.json"
462
+ return unless File.expand_path(config_path).start_with?(OPENC3_LOCAL_MODE_PATH)
463
+ FileUtils.rm_f(config_path)
457
464
  end
458
465
 
459
466
  def self.sync_settings()
@@ -471,6 +478,7 @@ module OpenC3
471
478
 
472
479
  def self.save_setting(scope, name, data)
473
480
  config_path = "#{OPENC3_LOCAL_MODE_PATH}/#{scope}/settings/#{name}.json"
481
+ return unless File.expand_path(config_path).start_with?(OPENC3_LOCAL_MODE_PATH)
474
482
  FileUtils.mkdir_p(File.dirname(config_path))
475
483
  # Anything can be stored as a setting so write it out directly
476
484
  File.write(config_path, data)
@@ -480,12 +488,14 @@ module OpenC3
480
488
 
481
489
  def self.sync_remote_to_local(bucket, key)
482
490
  local_path = "#{OPENC3_LOCAL_MODE_PATH}/#{key}"
491
+ return unless File.expand_path(local_path).start_with?(OPENC3_LOCAL_MODE_PATH)
483
492
  FileUtils.mkdir_p(File.dirname(local_path))
484
493
  bucket.get_object(bucket: ENV['OPENC3_CONFIG_BUCKET'], key: key, path: local_path)
485
494
  end
486
495
 
487
496
  def self.sync_local_to_remote(bucket, key)
488
497
  local_path = "#{OPENC3_LOCAL_MODE_PATH}/#{key}"
498
+ return unless File.expand_path(local_path).start_with?(OPENC3_LOCAL_MODE_PATH)
489
499
  File.open(local_path, 'rb') do |read_file|
490
500
  bucket.put_object(bucket: ENV['OPENC3_CONFIG_BUCKET'], key: key, body: read_file)
491
501
  end
@@ -493,6 +503,7 @@ module OpenC3
493
503
 
494
504
  def self.delete_local(key)
495
505
  local_path = "#{OPENC3_LOCAL_MODE_PATH}/#{key}"
506
+ return unless File.expand_path(local_path).start_with?(OPENC3_LOCAL_MODE_PATH)
496
507
  File.delete(local_path) if File.exist?(local_path)
497
508
  nil
498
509
  end
@@ -94,7 +94,13 @@ module OpenC3
94
94
  # First try opening a potentially modified version by looking for the modified target
95
95
  if ENV['OPENC3_LOCAL_MODE']
96
96
  local_file = OpenC3::LocalMode.open_local_file(name, scope: scope)
97
- return local_file.read if local_file
97
+ if local_file
98
+ if File.extname(name) == ".bin"
99
+ return local_file.read
100
+ else
101
+ return local_file.read.force_encoding('UTF-8')
102
+ end
103
+ end
98
104
  end
99
105
 
100
106
  bucket = Bucket.getClient()
@@ -106,8 +112,10 @@ module OpenC3
106
112
  if resp && resp.body
107
113
  if File.extname(name) == ".bin"
108
114
  resp.body.binmode
115
+ return resp.body.read
116
+ else
117
+ return resp.body.read.force_encoding('UTF-8')
109
118
  end
110
- resp.body.read
111
119
  else
112
120
  nil
113
121
  end
@@ -1,14 +1,14 @@
1
1
  # encoding: ascii-8bit
2
2
 
3
- OPENC3_VERSION = '6.0.2'
3
+ OPENC3_VERSION = '6.1.0'
4
4
  module OpenC3
5
5
  module Version
6
6
  MAJOR = '6'
7
- MINOR = '0'
8
- PATCH = '2'
7
+ MINOR = '1'
8
+ PATCH = '0'
9
9
  OTHER = ''
10
- BUILD = '30bcab7b66f6a10baacea338b83fc5bfb89e68d6'
10
+ BUILD = '4db8ce5a1e1178bf7272dab85bd71299e5d6c3b7'
11
11
  end
12
- VERSION = '6.0.2'
13
- GEM_VERSION = '6.0.2'
12
+ VERSION = '6.1.0'
13
+ GEM_VERSION = '6.1.0'
14
14
  end
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "<%= tool_name %>",
3
- "version": "6.0.2",
3
+ "version": "6.1.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.0.2",
26
+ "@openc3/js-common": "6.1.0",
27
27
  "rxjs": "~7.8.0",
28
28
  "single-spa": "^5.9.5",
29
29
  "single-spa-angular": "^9.2.0",
@@ -16,7 +16,7 @@
16
16
  "@emotion/react": "^11.13.3",
17
17
  "@emotion/styled": "^11.11.0",
18
18
  "@mui/material": "^6.1.1",
19
- "@openc3/js-common": "6.0.2",
19
+ "@openc3/js-common": "6.1.0",
20
20
  "react": "^18.2.0",
21
21
  "react-dom": "^18.2.0",
22
22
  "single-spa-react": "^5.1.4"
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "@astrouxds/astro-web-components": "^7.24.0",
15
- "@openc3/js-common": "6.0.2",
15
+ "@openc3/js-common": "6.1.0",
16
16
  "@smui/button": "^7.0.0",
17
17
  "@smui/common": "^7.0.0",
18
18
  "@smui/card": "^7.0.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "<%= tool_name %>",
3
- "version": "6.0.2",
3
+ "version": "6.1.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.0.2",
15
- "@openc3/vue-common": "6.0.2",
14
+ "@openc3/js-common": "6.1.0",
15
+ "@openc3/vue-common": "6.1.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.0.2",
3
+ "version": "6.1.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.0.2",
11
+ "@openc3/vue-common": "6.1.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.0.2
4
+ version: 6.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Melton
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2025-01-09 00:00:00.000000000 Z
12
+ date: 2025-02-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -791,6 +791,7 @@ files:
791
791
  - bin/cstol_converter
792
792
  - bin/openc3cli
793
793
  - bin/pipinstall
794
+ - bin/pipuninstall
794
795
  - bin/rubysloc
795
796
  - data/config/_array_params.yaml
796
797
  - data/config/_canvas_values.yaml
@@ -993,6 +994,7 @@ files:
993
994
  - lib/openc3/migrations/20231022000000_tlm_viewer_config.rb
994
995
  - lib/openc3/migrations/20241208080000_no_critical_cmd.rb
995
996
  - lib/openc3/migrations/20241208080001_no_trigger_group.rb
997
+ - lib/openc3/migrations/20250108060000_news_feed.rb
996
998
  - lib/openc3/models/activity_model.rb
997
999
  - lib/openc3/models/auth_model.rb
998
1000
  - lib/openc3/models/cvt_model.rb
@@ -1007,6 +1009,7 @@ files:
1007
1009
  - lib/openc3/models/microservice_status_model.rb
1008
1010
  - lib/openc3/models/migration_model.rb
1009
1011
  - lib/openc3/models/model.rb
1012
+ - lib/openc3/models/news_model.rb
1010
1013
  - lib/openc3/models/note_model.rb
1011
1014
  - lib/openc3/models/offline_access_model.rb
1012
1015
  - lib/openc3/models/ping_model.rb
@@ -1075,6 +1078,7 @@ files:
1075
1078
  - lib/openc3/script/suite.rb
1076
1079
  - lib/openc3/script/suite_results.rb
1077
1080
  - lib/openc3/script/suite_runner.rb
1081
+ - lib/openc3/script/tables.rb
1078
1082
  - lib/openc3/script/telemetry.rb
1079
1083
  - lib/openc3/script/web_socket_api.rb
1080
1084
  - lib/openc3/streams/mqtt_stream.rb