openc3 5.20.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
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