openc3 5.20.0 → 6.0.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.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/bin/openc3cli +12 -120
  3. data/data/config/command_modifiers.yaml +13 -1
  4. data/data/config/interface_modifiers.yaml +21 -4
  5. data/data/config/item_modifiers.yaml +1 -1
  6. data/data/config/microservice.yaml +15 -2
  7. data/data/config/param_item_modifiers.yaml +1 -1
  8. data/data/config/parameter_modifiers.yaml +1 -1
  9. data/data/config/table_manager.yaml +2 -2
  10. data/data/config/target.yaml +11 -0
  11. data/data/config/telemetry_modifiers.yaml +17 -1
  12. data/data/config/tool.yaml +12 -0
  13. data/data/config/widgets.yaml +13 -17
  14. data/lib/openc3/accessors/form_accessor.rb +4 -3
  15. data/lib/openc3/accessors/html_accessor.rb +3 -3
  16. data/lib/openc3/accessors/http_accessor.rb +13 -13
  17. data/lib/openc3/accessors/xml_accessor.rb +16 -4
  18. data/lib/openc3/api/target_api.rb +0 -30
  19. data/lib/openc3/config/config_parser.rb +6 -3
  20. data/lib/openc3/core_ext/array.rb +0 -16
  21. data/lib/openc3/core_ext.rb +0 -1
  22. data/lib/openc3/interfaces/file_interface.rb +198 -0
  23. data/lib/openc3/interfaces/http_client_interface.rb +71 -39
  24. data/lib/openc3/interfaces/http_server_interface.rb +0 -7
  25. data/lib/openc3/interfaces/interface.rb +2 -0
  26. data/lib/openc3/interfaces/mqtt_interface.rb +32 -15
  27. data/lib/openc3/interfaces/mqtt_stream_interface.rb +19 -4
  28. data/lib/openc3/interfaces/protocols/crc_protocol.rb +7 -0
  29. data/lib/openc3/interfaces/serial_interface.rb +1 -0
  30. data/lib/openc3/interfaces.rb +2 -4
  31. data/lib/openc3/microservices/multi_microservice.rb +3 -3
  32. data/lib/openc3/migrations/20241208080000_no_critical_cmd.rb +31 -0
  33. data/lib/openc3/migrations/20241208080001_no_trigger_group.rb +46 -0
  34. data/lib/openc3/models/interface_model.rb +9 -3
  35. data/lib/openc3/models/microservice_model.rb +8 -1
  36. data/lib/openc3/models/plugin_model.rb +6 -1
  37. data/lib/openc3/models/python_package_model.rb +6 -1
  38. data/lib/openc3/models/reaction_model.rb +14 -10
  39. data/lib/openc3/models/scope_model.rb +60 -42
  40. data/lib/openc3/models/target_model.rb +17 -1
  41. data/lib/openc3/models/timeline_model.rb +17 -5
  42. data/lib/openc3/models/tool_model.rb +15 -3
  43. data/lib/openc3/models/trigger_group_model.rb +6 -3
  44. data/lib/openc3/operators/microservice_operator.rb +8 -0
  45. data/lib/openc3/packets/commands.rb +17 -6
  46. data/lib/openc3/packets/limits.rb +0 -12
  47. data/lib/openc3/packets/packet.rb +1 -1
  48. data/lib/openc3/packets/packet_item.rb +30 -36
  49. data/lib/openc3/packets/structure_item.rb +2 -2
  50. data/lib/openc3/script/script.rb +0 -10
  51. data/lib/openc3/script/web_socket_api.rb +2 -2
  52. data/lib/openc3/streams/mqtt_stream.rb +41 -33
  53. data/lib/openc3/streams/serial_stream.rb +27 -27
  54. data/lib/openc3/streams/stream.rb +17 -17
  55. data/lib/openc3/streams/tcpip_client_stream.rb +1 -1
  56. data/lib/openc3/streams/tcpip_socket_stream.rb +19 -19
  57. data/lib/openc3/system/system.rb +1 -1
  58. data/lib/openc3/system.rb +2 -3
  59. data/lib/openc3/tools/table_manager/table.rb +2 -2
  60. data/lib/openc3/tools/table_manager/table_parser.rb +1 -1
  61. data/lib/openc3/top_level.rb +0 -5
  62. data/lib/openc3/topics/command_decom_topic.rb +0 -7
  63. data/lib/openc3/utilities/bucket_utilities.rb +1 -1
  64. data/lib/openc3/utilities/cli_generator.rb +0 -1
  65. data/lib/openc3/version.rb +6 -6
  66. data/templates/plugin/README.md +1 -1
  67. data/templates/target/targets/TARGET/lib/target.rb +1 -1
  68. data/templates/tool_angular/package.json +8 -8
  69. data/templates/tool_angular/src/app/app.component.html +4 -13
  70. data/templates/tool_angular/src/app/app.component.scss +5 -13
  71. data/templates/tool_angular/src/app/app.component.ts +5 -4
  72. data/templates/tool_angular/src/app/custom-overlay-container.ts +2 -2
  73. data/templates/tool_angular/src/app/openc3-api.d.ts +1 -1
  74. data/templates/tool_angular/src/main.single-spa.ts +1 -1
  75. data/templates/tool_react/package.json +1 -0
  76. data/templates/tool_react/src/root.component.js +1 -1
  77. data/templates/tool_svelte/package.json +11 -9
  78. data/templates/tool_svelte/rollup.config.js +2 -0
  79. data/templates/tool_svelte/src/App.svelte +2 -2
  80. data/templates/tool_vue/eslint.config.mjs +68 -0
  81. data/templates/tool_vue/jsconfig.json +1 -1
  82. data/templates/tool_vue/package.json +26 -43
  83. data/templates/tool_vue/src/App.vue +3 -5
  84. data/templates/tool_vue/src/main.js +12 -23
  85. data/templates/tool_vue/src/router.js +19 -18
  86. data/templates/tool_vue/src/tools/tool_name/tool_name.vue +2 -2
  87. data/templates/tool_vue/vite.config.js +52 -0
  88. data/templates/widget/package.json +19 -26
  89. data/templates/widget/src/Widget.vue +13 -15
  90. data/templates/widget/vite.config.js +26 -0
  91. metadata +10 -41
  92. data/lib/openc3/core_ext/hash.rb +0 -40
  93. data/lib/openc3/core_ext/httpclient.rb +0 -11
  94. data/lib/openc3/interfaces/linc_interface.rb +0 -480
  95. data/lib/openc3/interfaces/protocols/override_protocol.rb +0 -4
  96. data/lib/openc3/microservices/critical_cmd_microservice.rb +0 -74
  97. data/lib/openc3/microservices/reaction_microservice.rb +0 -607
  98. data/lib/openc3/microservices/timeline_microservice.rb +0 -398
  99. data/lib/openc3/microservices/trigger_group_microservice.rb +0 -698
  100. data/lib/openc3/migrations/20230615000000_autonomic.rb +0 -86
  101. data/lib/openc3/migrations/20240915000000_activity_uuid.rb +0 -28
  102. data/lib/openc3/migrations/20241016000000_scope_critical_cmd.rb +0 -24
  103. data/lib/openc3/system/system_config.rb +0 -413
  104. data/templates/tool_svelte/src/services/api.js +0 -92
  105. data/templates/tool_svelte/src/services/axios.js +0 -85
  106. data/templates/tool_svelte/src/services/cable.js +0 -65
  107. data/templates/tool_svelte/src/services/config-parser.js +0 -198
  108. data/templates/tool_svelte/src/services/openc3-api.js +0 -606
  109. data/templates/tool_vue/.eslintrc.js +0 -43
  110. data/templates/tool_vue/babel.config.json +0 -11
  111. data/templates/tool_vue/vue.config.js +0 -38
  112. data/templates/widget/.eslintrc.js +0 -43
  113. data/templates/widget/babel.config.json +0 -11
  114. data/templates/widget/vue.config.js +0 -28
  115. /data/templates/tool_vue/{.prettierrc.js → .prettierrc.cjs} +0 -0
  116. /data/templates/widget/{.prettierrc.js → .prettierrc.cjs} +0 -0
@@ -1,398 +0,0 @@
1
- # encoding: ascii-8bit
2
-
3
- # Copyright 2022 Ball Aerospace & Technologies Corp.
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
- # Modified by OpenC3, Inc.
17
- # All changes Copyright 2023, OpenC3, Inc.
18
- # All Rights Reserved
19
- #
20
- # This file may also be used under the terms of a commercial license
21
- # if purchased from OpenC3, Inc.
22
-
23
- require 'openc3/utilities/authentication'
24
- require 'openc3/microservices/microservice'
25
- require 'openc3/models/activity_model'
26
- require 'openc3/models/timeline_model'
27
- require 'openc3/models/tool_config_model'
28
- require 'openc3/topics/timeline_topic'
29
-
30
- require 'openc3/script'
31
-
32
- module OpenC3
33
- # The Timeline worker is a very simple thread pool worker. Once
34
- # the timeline manager has pushed a job to the schedule one of
35
- # these workers will run the CMD (command) or SCRIPT (script)
36
- # or anything that could be expanded in the future.
37
- class TimelineWorker
38
- def initialize(name:, logger:, scope:, queue:)
39
- @timeline_name = name
40
- @logger = logger
41
- @scope = scope
42
- @queue = queue
43
- end
44
-
45
- def get_token(username)
46
- if ENV['OPENC3_API_CLIENT'].nil?
47
- ENV['OPENC3_API_PASSWORD'] ||= ENV['OPENC3_SERVICE_PASSWORD']
48
- return OpenC3Authentication.new().token
49
- else
50
- # Check for offline access token
51
- model = nil
52
- model = OpenC3::OfflineAccessModel.get_model(name: username, scope: @scope) if username and username != ''
53
- if model and model.offline_access_token
54
- auth = OpenC3KeycloakAuthentication.new(ENV['OPENC3_KEYCLOAK_URL'])
55
- return auth.get_token_from_refresh_token(model.offline_access_token)
56
- else
57
- return nil
58
- end
59
- end
60
- end
61
-
62
- def run
63
- @logger.info "#{@timeline_name} timeline worker running"
64
- loop do
65
- activity = @queue.pop
66
- break if activity.nil?
67
-
68
- run_activity(activity)
69
- end
70
- @logger.info "#{@timeline_name} timeline worker exiting"
71
- end
72
-
73
- def run_activity(activity)
74
- case activity.kind.downcase
75
- when 'command'
76
- run_command(activity)
77
- when 'script'
78
- run_script(activity)
79
- when 'expire'
80
- clear_expired(activity)
81
- else
82
- @logger.error "Unknown kind passed to microservice #{@timeline_name}: #{activity.as_json(:allow_nan => true)}"
83
- end
84
- end
85
-
86
- def get_exec_setting()
87
- json = ToolConfigModel.load_config('calendar-settings', 'default', scope: @scope)
88
- if json
89
- settings = JSON.parse(json)
90
- return settings['execEnabled']
91
- else
92
- # Default is execute
93
- return true
94
- end
95
- end
96
-
97
- def run_command(activity)
98
- @logger.info "#{@timeline_name} run_command > #{activity.as_json(:allow_nan => true)}"
99
- begin
100
- if get_exec_setting()
101
- username = activity.data['username']
102
- token = get_token(username)
103
- raise "No token available for username: #{username}" unless token
104
- cmd_no_hazardous_check(activity.data['command'], scope: @scope, token: token)
105
- activity.commit(status: 'completed', fulfillment: true)
106
- else
107
- activity.commit(status: 'disabled', message: 'Execution is disabled')
108
- @logger.warn "#{@timeline_name} run_command disabled > #{activity.as_json(:allow_nan => true)}"
109
- end
110
- rescue StandardError => e
111
- activity.commit(status: 'failed', message: e.message)
112
- @logger.error "#{@timeline_name} run_command failed > #{activity.as_json(:allow_nan => true)}, #{e.formatted}"
113
- end
114
- end
115
-
116
- def run_script(activity)
117
- @logger.info "#{@timeline_name} run_script > #{activity.as_json(:allow_nan => true)}"
118
- begin
119
- if get_exec_setting()
120
- username = activity.data['username']
121
- token = get_token(username)
122
- raise "No token available for username: #{username}" unless token
123
- request = Net::HTTP::Post.new(
124
- "/script-api/scripts/#{activity.data['script']}/run?scope=#{@scope}",
125
- 'Content-Type' => 'application/json',
126
- 'Authorization' => token
127
- )
128
- request.body = JSON.generate({
129
- 'scope' => @scope,
130
- 'environment' => activity.data['environment'],
131
- 'timeline' => @timeline_name,
132
- 'id' => activity.start
133
- })
134
- hostname = ENV['OPENC3_SCRIPT_HOSTNAME'] || 'openc3-cosmos-script-runner-api'
135
- response = Net::HTTP.new(hostname, 2902).request(request)
136
- raise "failed to call #{hostname}, for script: #{activity.data['script']}, response code: #{response.code}" if response.code != '200'
137
-
138
- activity.commit(status: 'completed', message: "#{activity.data['script']} => #{response.body}", fulfillment: true)
139
- else
140
- activity.commit(status: 'disabled', message: 'Execution is disabled')
141
- @logger.warn "#{@timeline_name} run_script disabled > #{activity.as_json(:allow_nan => true)}"
142
- end
143
- rescue StandardError => e
144
- activity.commit(status: 'failed', message: e.message)
145
- @logger.error "#{@timeline_name} run_script failed > #{activity.as_json(:allow_nan => true)}, #{e.message}"
146
- end
147
- end
148
-
149
- def clear_expired(activity)
150
- begin
151
- num = ActivityModel.range_destroy(name: @timeline_name, scope: @scope, min: activity.start, max: activity.stop)
152
- @logger.info "#{@timeline_name} clear_expired removed #{num} items from #{activity.start} to #{activity.stop}"
153
- activity.add_event(status: 'completed')
154
- rescue StandardError => e
155
- @logger.error "#{@timeline_name} clear_expired failed > #{activity.as_json(:allow_nan => true)} #{e.message}"
156
- end
157
- end
158
- end
159
-
160
- # Shared between the monitor thread and the manager thread to
161
- # share the planned activities. This should remain a thread
162
- # safe implementation.
163
- class Schedule
164
- def initialize(name)
165
- @name = name
166
- @activities_mutex = Mutex.new
167
- @activities = []
168
- # This can stay relatively small since it's only the currently queued
169
- # activities. Once a worker thread processes it we no longer care.
170
- @size = 20
171
- @queue = Array.new(@size)
172
- @index = 0
173
- end
174
-
175
- def not_queued?(activity)
176
- return false if @queue.index(activity.uuid)
177
- @queue[@index] = activity.uuid
178
- @index = @index + 1 >= @size ? 0 : @index + 1
179
- return true
180
- end
181
-
182
- def activities
183
- @activities_mutex.synchronize do
184
- return @activities.dup
185
- end
186
- end
187
-
188
- def update(input_activities)
189
- @activities_mutex.synchronize do
190
- @activities = input_activities.dup
191
- end
192
- end
193
-
194
- def add_activity(activity)
195
- @activities_mutex.synchronize do
196
- @activities << activity
197
- end
198
- end
199
-
200
- def remove_activity(data)
201
- @activities_mutex.synchronize do
202
- @activities.delete_if do |a|
203
- a.start == data['start'] && a.uuid == data['uuid']
204
- end
205
- end
206
- end
207
- end
208
-
209
- # The timeline manager starts a thread pool and looks at the
210
- # schedule and if an "activity" should be run. TimelineManager
211
- # adds the "activity" to the thread pool and the thread will
212
- # execute the "activity".
213
- class TimelineManager
214
- def initialize(name:, logger:, scope:, schedule:)
215
- @timeline_name = name
216
- @logger = logger
217
- @scope = scope
218
- @schedule = schedule
219
- @worker_count = 3
220
- @queue = Queue.new
221
- @thread_pool = generate_thread_pool()
222
- @cancel_thread = false
223
- @expire = 0
224
- end
225
-
226
- def generate_thread_pool
227
- thread_pool = []
228
- @worker_count.times {
229
- worker = TimelineWorker.new(name: @timeline_name, logger: @logger, scope: @scope, queue: @queue)
230
- thread_pool << Thread.new { worker.run }
231
- }
232
- return thread_pool
233
- end
234
-
235
- def run
236
- @logger.info "#{@timeline_name} timeline manager running"
237
- loop do
238
- start = Time.now.to_f
239
- @schedule.activities.each do |activity|
240
- start_difference = activity.start - start
241
- if start_difference <= 0 && @schedule.not_queued?(activity)
242
- @logger.debug "#{@timeline_name} #{@scope} current start: #{start}, vs #{activity.start}, #{start_difference}"
243
- activity.add_event(status: 'queued')
244
- @queue << activity
245
- end
246
- end
247
- if start >= @expire
248
- @queue << add_expire_activity()
249
- request_update(start: start)
250
- end
251
- break if @cancel_thread
252
-
253
- sleep(1)
254
- break if @cancel_thread
255
- end
256
- @logger.info "#{@timeline_name} timeline manager exiting"
257
- end
258
-
259
- # Add task to remove events older than 7 days
260
- def add_expire_activity
261
- now = Time.now.to_f
262
- @expire = now + 3540 # Needs to be less than 3600 which is the hour we store in memory
263
- activity = ActivityModel.new(
264
- name: @timeline_name,
265
- scope: @scope,
266
- start: 0,
267
- stop: (now - (86_400 * 7)),
268
- kind: 'expire',
269
- data: {}
270
- )
271
- return activity
272
- end
273
-
274
- # This can feedback to ensure the schedule will not run out so this should fire once an
275
- # hour to make sure the TimelineMicroservice will collect the next hour and update the
276
- # schedule.
277
- def request_update(start:)
278
- notification = {
279
- 'data' => JSON.generate({ 'time' => start }),
280
- 'kind' => 'refresh',
281
- 'type' => 'timeline',
282
- 'timeline' => @timeline_name
283
- }
284
- begin
285
- TimelineTopic.write_activity(notification, scope: @scope)
286
- rescue StandardError
287
- @logger.error "#{@name} manager failed to request update"
288
- end
289
- end
290
-
291
- def shutdown
292
- @cancel_thread = true
293
- @worker_count.times {
294
- @queue << nil
295
- }
296
- end
297
- end
298
-
299
- # The timeline microservice starts a manager then gets the activities
300
- # from the sorted set in redis and updates the schedule for the
301
- # manager. Timeline will then wait for an update on the timeline
302
- # stream this will trigger an update again to the schedule.
303
- class TimelineMicroservice < Microservice
304
- def initialize(name)
305
- super(name)
306
- @timeline_name = name.split('__')[2]
307
- @schedule = Schedule.new(@timeline_name)
308
- @manager = TimelineManager.new(name: @timeline_name, logger: @logger, scope: @scope, schedule: @schedule)
309
- @manager_thread = nil
310
- @read_topic = true
311
- end
312
-
313
- def run
314
- @logger.info "#{@name} timeline running"
315
- @manager_thread = Thread.new { @manager.run }
316
- loop do
317
- current_activities = ActivityModel.activities(name: @timeline_name, scope: @scope)
318
- @schedule.update(current_activities)
319
- break if @cancel_thread
320
-
321
- block_for_updates()
322
- break if @cancel_thread
323
- end
324
- @logger.info "#{@name} timeline exiting"
325
- end
326
-
327
- def topic_lookup_functions
328
- {
329
- 'timeline' => {
330
- 'created' => :timeline_noop,
331
- 'refresh' => :schedule_refresh,
332
- 'updated' => :timeline_noop,
333
- 'deleted' => :timeline_noop
334
- },
335
- 'activity' => {
336
- 'event' => :timeline_noop,
337
- 'created' => :create_activity_from_event,
338
- 'updated' => :schedule_refresh,
339
- 'deleted' => :remove_activity_from_event
340
- }
341
- }
342
- end
343
-
344
- def block_for_updates
345
- @read_topic = true
346
- while @read_topic
347
- begin
348
- TimelineTopic.read_topics(@topics) do |_topic, _msg_id, msg_hash, _redis|
349
- if msg_hash['timeline'] == @timeline_name
350
- data = JSON.parse(msg_hash['data'], :allow_nan => true, :create_additions => true)
351
- public_send(topic_lookup_functions[msg_hash['type']][msg_hash['kind']], data)
352
- end
353
- end
354
- rescue StandardError => e
355
- @logger.error "#{@timeline_name} failed to read topics #{@topics}\n#{e.formatted}"
356
- end
357
- end
358
- end
359
-
360
- def timeline_noop(data)
361
- @logger.debug "#{@name} timeline web socket event: #{data}"
362
- end
363
-
364
- def schedule_refresh(data)
365
- @logger.debug "#{@name} timeline web socket schedule refresh: #{data}"
366
- @read_topic = false
367
- end
368
-
369
- # Add the activity to the schedule. We don't need to hold the job in memory
370
- # if it is longer than an hour away. A refresh task will update that.
371
- def create_activity_from_event(data)
372
- diff = data['start'] - Time.now.to_f
373
- return if diff < 0 or diff > 3600
374
-
375
- activity = ActivityModel.from_json(data, name: @timeline_name, scope: @scope)
376
- @schedule.add_activity(activity)
377
- end
378
-
379
- # Remove the activity from the schedule. We don't need to remove the activity
380
- # if it is longer than an hour away. It will be removed from the data.
381
- def remove_activity_from_event(data)
382
- diff = data['start'] - Time.now.to_f
383
- return if diff < 0 or diff > 3600
384
- @schedule.remove_activity(data)
385
- end
386
-
387
- def shutdown
388
- @manager.shutdown
389
- # super also sets @cancel_thread = true but we want to set it first
390
- # so when we set @read_topic = false the run loop stops
391
- @cancel_thread = true
392
- @read_topic = false
393
- super()
394
- end
395
- end
396
- end
397
-
398
- OpenC3::TimelineMicroservice.run if __FILE__ == $0