openc3 5.18.0 → 5.19.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +7 -4
- data/bin/cstol_converter +14 -14
- data/bin/openc3cli +189 -7
- data/data/config/_interfaces.yaml +1 -1
- data/data/config/command_modifiers.yaml +55 -0
- data/data/config/interface_modifiers.yaml +1 -1
- data/data/config/param_item_modifiers.yaml +1 -1
- data/data/config/parameter_modifiers.yaml +1 -1
- data/data/config/plugins.yaml +6 -2
- data/data/config/screen.yaml +2 -2
- data/data/config/table_manager.yaml +2 -2
- data/data/config/tool.yaml +4 -1
- data/data/config/widgets.yaml +3 -3
- data/ext/openc3/ext/config_parser/config_parser.c +1 -1
- data/ext/openc3/ext/packet/packet.c +1 -1
- data/ext/openc3/ext/platform/platform.c +3 -3
- data/ext/openc3/ext/structure/structure.c +56 -76
- data/lib/openc3/accessors/binary_accessor.rb +4 -4
- data/lib/openc3/accessors/form_accessor.rb +2 -2
- data/lib/openc3/accessors/http_accessor.rb +1 -1
- data/lib/openc3/accessors/json_accessor.rb +6 -4
- data/lib/openc3/accessors/template_accessor.rb +6 -9
- data/lib/openc3/accessors/xml_accessor.rb +1 -1
- data/lib/openc3/api/cmd_api.rb +35 -11
- data/lib/openc3/api/limits_api.rb +1 -1
- data/lib/openc3/config/config_parser.rb +1 -1
- data/lib/openc3/conversions/segmented_polynomial_conversion.rb +7 -7
- data/lib/openc3/core_ext/array.rb +5 -5
- data/lib/openc3/core_ext/exception.rb +9 -2
- data/lib/openc3/core_ext/string.rb +2 -2
- data/lib/openc3/interfaces/http_server_interface.rb +1 -0
- data/lib/openc3/interfaces/interface.rb +1 -1
- data/lib/openc3/interfaces/linc_interface.rb +3 -3
- data/lib/openc3/io/json_api.rb +11 -6
- data/lib/openc3/io/json_rpc.rb +1 -1
- data/lib/openc3/logs/buffered_packet_log_writer.rb +3 -3
- data/lib/openc3/logs/log_writer.rb +7 -8
- data/lib/openc3/logs/packet_log_writer.rb +7 -7
- data/lib/openc3/logs/text_log_writer.rb +4 -4
- data/lib/openc3/microservices/decom_microservice.rb +19 -4
- data/lib/openc3/microservices/interface_microservice.rb +41 -3
- data/lib/openc3/microservices/reaction_microservice.rb +2 -2
- data/lib/openc3/microservices/trigger_group_microservice.rb +3 -3
- data/lib/openc3/migrations/20240915000000_activity_uuid.rb +28 -0
- data/lib/openc3/models/activity_model.rb +109 -80
- data/lib/openc3/models/auth_model.rb +31 -2
- data/lib/openc3/models/cvt_model.rb +11 -5
- data/lib/openc3/models/gem_model.rb +8 -8
- data/lib/openc3/models/plugin_model.rb +3 -3
- data/lib/openc3/models/reducer_model.rb +2 -2
- data/lib/openc3/models/scope_model.rb +1 -1
- data/lib/openc3/models/sorted_model.rb +4 -4
- data/lib/openc3/models/target_model.rb +3 -3
- data/lib/openc3/models/tool_config_model.rb +1 -1
- data/lib/openc3/models/tool_model.rb +4 -4
- data/lib/openc3/models/widget_model.rb +11 -5
- data/lib/openc3/operators/operator.rb +5 -3
- data/lib/openc3/packets/command_validator.rb +48 -0
- data/lib/openc3/packets/commands.rb +6 -14
- data/lib/openc3/packets/packet.rb +31 -15
- data/lib/openc3/packets/packet_config.rb +10 -9
- data/lib/openc3/packets/parsers/packet_parser.rb +3 -3
- data/lib/openc3/packets/structure.rb +21 -13
- data/lib/openc3/packets/structure_item.rb +33 -47
- data/lib/openc3/packets/telemetry.rb +6 -27
- data/lib/openc3/script/api_shared.rb +7 -5
- data/lib/openc3/script/calendar.rb +2 -2
- data/lib/openc3/script/commands.rb +6 -4
- data/lib/openc3/script/metadata.rb +2 -2
- data/lib/openc3/script/suite.rb +17 -17
- data/lib/openc3/streams/serial_stream.rb +2 -3
- data/lib/openc3/streams/stream.rb +2 -2
- data/lib/openc3/tools/cmd_tlm_server/interface_thread.rb +10 -10
- data/lib/openc3/tools/table_manager/table_manager_core.rb +11 -11
- data/lib/openc3/tools/table_manager/table_parser.rb +2 -3
- data/lib/openc3/topics/command_decom_topic.rb +2 -1
- data/lib/openc3/topics/command_topic.rb +3 -3
- data/lib/openc3/topics/decom_interface_topic.rb +2 -2
- data/lib/openc3/topics/telemetry_decom_topic.rb +1 -1
- data/lib/openc3/utilities/authorization.rb +2 -1
- data/lib/openc3/utilities/cli_generator.rb +15 -8
- data/lib/openc3/utilities/cosmos_rails_formatter.rb +60 -0
- data/lib/openc3/utilities/crc.rb +6 -6
- data/lib/openc3/utilities/local_mode.rb +2 -1
- data/lib/openc3/utilities/logger.rb +44 -34
- data/lib/openc3/utilities/metric.rb +1 -2
- data/lib/openc3/utilities/quaternion.rb +18 -18
- data/lib/openc3/utilities/target_file.rb +4 -4
- data/lib/openc3/version.rb +5 -5
- data/lib/openc3/win32/win32_main.rb +2 -2
- data/templates/tool_angular/package.json +21 -21
- data/templates/tool_react/package.json +10 -10
- data/templates/tool_svelte/package.json +11 -11
- data/templates/tool_svelte/src/services/openc3-api.js +17 -17
- data/templates/tool_vue/package.json +9 -9
- data/templates/widget/package.json +6 -7
- metadata +5 -2
@@ -47,41 +47,28 @@ module OpenC3
|
|
47
47
|
start_score = now - 15
|
48
48
|
stop_score = (now + 3660)
|
49
49
|
array = Store.zrangebyscore("#{scope}#{PRIMARY_KEY}__#{name}", start_score, stop_score)
|
50
|
-
|
51
|
-
array.each do |value|
|
52
|
-
ret_array << ActivityModel.from_json(value, name: name, scope: scope)
|
53
|
-
end
|
54
|
-
return ret_array
|
50
|
+
return array.map { |value| ActivityModel.from_json(value, name: name, scope: scope) }
|
55
51
|
end
|
56
52
|
|
57
53
|
# @return [Array|nil] Array up to 100 of this model or empty array if name not found under primary_key
|
58
54
|
def self.get(name:, start:, stop:, scope:, limit: 100)
|
59
55
|
if start > stop
|
60
|
-
raise ActivityInputError.new "start: #{start} must be
|
56
|
+
raise ActivityInputError.new "start: #{start} must be <= stop: #{stop}"
|
61
57
|
end
|
62
|
-
|
63
58
|
array = Store.zrangebyscore("#{scope}#{PRIMARY_KEY}__#{name}", start, stop, :limit => [0, limit])
|
64
|
-
|
65
|
-
array.each do |value|
|
66
|
-
ret_array << JSON.parse(value, :allow_nan => true, :create_additions => true)
|
67
|
-
end
|
68
|
-
return ret_array
|
59
|
+
return array.map { |value| JSON.parse(value, :allow_nan => true, :create_additions => true) }
|
69
60
|
end
|
70
61
|
|
71
62
|
# @return [Array<Hash>] Array up to the limit of the models (as Hash objects) stored under the primary key
|
72
63
|
def self.all(name:, scope:, limit: 100)
|
73
64
|
array = Store.zrange("#{scope}#{PRIMARY_KEY}__#{name}", 0, -1, :limit => [0, limit])
|
74
|
-
|
75
|
-
array.each do |value|
|
76
|
-
ret_array << JSON.parse(value, :allow_nan => true, :create_additions => true)
|
77
|
-
end
|
78
|
-
return ret_array
|
65
|
+
return array.map { |value| JSON.parse(value, :allow_nan => true, :create_additions => true) }
|
79
66
|
end
|
80
67
|
|
81
68
|
# @return [String|nil] String of the saved json or nil if score not found under primary_key
|
82
69
|
def self.score(name:, score:, scope:)
|
83
|
-
|
84
|
-
|
70
|
+
value = Store.zrangebyscore("#{scope}#{PRIMARY_KEY}__#{name}", score, score, :limit => [0, 1]).first
|
71
|
+
if value
|
85
72
|
return ActivityModel.from_json(value, name: name, scope: scope)
|
86
73
|
end
|
87
74
|
return nil
|
@@ -93,9 +80,45 @@ module OpenC3
|
|
93
80
|
end
|
94
81
|
|
95
82
|
# Remove one member from a sorted set.
|
96
|
-
# @return [Integer] count of the members removed
|
97
|
-
def self.destroy(name:, scope:, score:)
|
98
|
-
result =
|
83
|
+
# @return [Integer] count of the members removed, 0 indicates the member was not found
|
84
|
+
def self.destroy(name:, scope:, score:, uuid: nil, recurring: nil)
|
85
|
+
result = 0
|
86
|
+
|
87
|
+
# Delete all recurring activities
|
88
|
+
if recurring
|
89
|
+
activity = self.score(name: name, score: score, scope: scope)
|
90
|
+
if activity and activity.recurring['end'] and activity.recurring['uuid']
|
91
|
+
json = Store.zrangebyscore("#{scope}#{PRIMARY_KEY}__#{name}", activity.recurring['start'], activity.recurring['end'])
|
92
|
+
parsed = json.map { |value| ActivityModel.from_json(value, name: name, scope: scope) }
|
93
|
+
parsed.each_with_index do |value, index|
|
94
|
+
if value.recurring['uuid'] == uuid
|
95
|
+
Store.zrem("#{scope}#{PRIMARY_KEY}__#{name}", json[index])
|
96
|
+
result += 1
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# First find all the activities at the score
|
103
|
+
json = Store.zrangebyscore("#{scope}#{PRIMARY_KEY}__#{name}", score, score, :limit => [0, 100])
|
104
|
+
parsed = json.map { |value| JSON.parse(value, :allow_nan => true, :create_additions => true) }
|
105
|
+
parsed.each_with_index do |value, index|
|
106
|
+
if uuid
|
107
|
+
# If the uuid is given then only delete activities that match the uuid
|
108
|
+
if value['uuid'] == uuid
|
109
|
+
Store.zrem("#{scope}#{PRIMARY_KEY}__#{name}", json[index])
|
110
|
+
result += 1
|
111
|
+
break
|
112
|
+
end
|
113
|
+
else
|
114
|
+
# If the uuid is not given (backwards compatibility) then delete all activities
|
115
|
+
# at the score that do NOT have a uuid
|
116
|
+
next if value['uuid']
|
117
|
+
Store.zrem("#{scope}#{PRIMARY_KEY}__#{name}", json[index])
|
118
|
+
result += 1
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
99
122
|
notification = {
|
100
123
|
# start / stop to match SortedModel
|
101
124
|
'data' => JSON.generate({'start' => score}),
|
@@ -129,27 +152,30 @@ module OpenC3
|
|
129
152
|
self.new(**json.transform_keys(&:to_sym), name: name, scope: scope)
|
130
153
|
end
|
131
154
|
|
132
|
-
attr_reader :start, :stop, :kind, :data, :
|
155
|
+
attr_reader :start, :stop, :kind, :data, :fulfillment, :uuid, :events, :recurring
|
133
156
|
|
134
157
|
def initialize(
|
135
|
-
name:,
|
158
|
+
name:, # part of Model
|
136
159
|
start:,
|
137
160
|
stop:,
|
138
161
|
kind:,
|
139
162
|
data:,
|
140
|
-
scope:,
|
141
|
-
updated_at: 0,
|
163
|
+
scope:, # part of Model
|
164
|
+
updated_at: 0, # part of Model
|
142
165
|
fulfillment: nil,
|
166
|
+
uuid: nil,
|
143
167
|
events: nil,
|
144
168
|
recurring: {}
|
145
169
|
)
|
146
170
|
super("#{scope}#{PRIMARY_KEY}__#{name}", name: name, scope: scope)
|
171
|
+
# Validate everything that isn't already in Model
|
147
172
|
set_input(
|
148
|
-
fulfillment: fulfillment,
|
149
173
|
start: start,
|
150
174
|
stop: stop,
|
151
175
|
kind: kind,
|
152
176
|
data: data,
|
177
|
+
fulfillment: fulfillment,
|
178
|
+
uuid: uuid,
|
153
179
|
events: events,
|
154
180
|
recurring: recurring,
|
155
181
|
)
|
@@ -162,19 +188,19 @@ module OpenC3
|
|
162
188
|
# need to return all the activities that may start before us and verify that we don't overlap them.
|
163
189
|
# Activities are only inserted by @start time so we need to go back to verify we don't overlap existing @stop.
|
164
190
|
# Note: Score is the Seconds since the Unix Epoch: (%s) Number of seconds since 1970-01-01 00:00:00 UTC.
|
165
|
-
# zrange rev byscore finds
|
191
|
+
# zrange rev byscore finds activities from in reverse order so the first task is the closest task to the current score.
|
166
192
|
# In this case a parameter ignore_score allows the request to ignore that time and skip to the next time
|
167
193
|
# but if nothing is found in the time range we can return nil.
|
168
194
|
#
|
169
195
|
# @param [Integer] ignore_score - should be nil unless you want to ignore a time when doing an update
|
170
|
-
def validate_time(ignore_score
|
196
|
+
def validate_time(start, stop, ignore_score: nil)
|
171
197
|
# Adding a '(' makes the max value exclusive
|
172
|
-
array = Store.zrevrangebyscore(@primary_key, "(#{
|
198
|
+
array = Store.zrevrangebyscore(@primary_key, "(#{stop}", start - MAX_DURATION)
|
173
199
|
array.each do |value|
|
174
200
|
activity = JSON.parse(value, :allow_nan => true, :create_additions => true)
|
175
201
|
if ignore_score == activity['start']
|
176
202
|
next
|
177
|
-
elsif activity['stop'] >
|
203
|
+
elsif activity['stop'] > start
|
178
204
|
return activity['start']
|
179
205
|
else
|
180
206
|
return nil
|
@@ -204,7 +230,7 @@ module OpenC3
|
|
204
230
|
end
|
205
231
|
if now_f >= start and kind != 'expire'
|
206
232
|
raise ActivityInputError.new "activity must be in the future, current_time: #{now_f} vs #{start}"
|
207
|
-
elsif duration
|
233
|
+
elsif duration > MAX_DURATION and kind != 'expire'
|
208
234
|
raise ActivityInputError.new "activity can not be longer than #{MAX_DURATION} seconds"
|
209
235
|
elsif duration <= 0
|
210
236
|
raise ActivityInputError.new "start: #{start} must be before stop: #{stop}"
|
@@ -218,7 +244,7 @@ module OpenC3
|
|
218
244
|
end
|
219
245
|
|
220
246
|
# Set the values of the instance, @start, @kind, @data, @events...
|
221
|
-
def set_input(start:, stop:, kind: nil, data: nil, events: nil, fulfillment: nil, recurring: nil)
|
247
|
+
def set_input(start:, stop:, kind: nil, data: nil, uuid: nil, events: nil, fulfillment: nil, recurring: nil)
|
222
248
|
kind = kind.to_s.downcase
|
223
249
|
validate_input(start: start, stop: stop, kind: kind, data: data)
|
224
250
|
@start = start
|
@@ -226,6 +252,7 @@ module OpenC3
|
|
226
252
|
@fulfillment = fulfillment.nil? ? false : fulfillment
|
227
253
|
@kind = kind
|
228
254
|
@data = data.nil? ? @data : data
|
255
|
+
@uuid = uuid.nil? ? SecureRandom.uuid : uuid
|
229
256
|
@events = events.nil? ? Array.new : events
|
230
257
|
@recurring = recurring.nil? ? @recurring : recurring
|
231
258
|
end
|
@@ -241,20 +268,22 @@ module OpenC3
|
|
241
268
|
@recurring['uuid'] = SecureRandom.uuid
|
242
269
|
@recurring['start'] = @start
|
243
270
|
duration = @stop - @start
|
244
|
-
|
271
|
+
recurrence = 0
|
245
272
|
case @recurring['span']
|
246
273
|
when 'minutes'
|
247
|
-
|
274
|
+
recurrence = @recurring['frequency'].to_i * 60
|
248
275
|
when 'hours'
|
249
|
-
|
276
|
+
recurrence = @recurring['frequency'].to_i * 3600
|
250
277
|
when 'days'
|
251
|
-
|
278
|
+
recurrence = @recurring['frequency'].to_i * 86400
|
252
279
|
end
|
253
280
|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
281
|
+
unless overlap
|
282
|
+
# Get all the existing events in the recurring time range as well as those before
|
283
|
+
# the start of the recurring time range to ensure we don't start inside an existing event
|
284
|
+
existing = Store.zrevrangebyscore(@primary_key, @recurring['end'] - 1, @recurring['start'] - MAX_DURATION)
|
285
|
+
existing.map! {|value| JSON.parse(value, :allow_nan => true, :create_additions => true) }
|
286
|
+
end
|
258
287
|
last_stop = nil
|
259
288
|
|
260
289
|
# Update @updated_at and add an event assuming it all completes ok
|
@@ -262,19 +291,21 @@ module OpenC3
|
|
262
291
|
add_event(status: 'created')
|
263
292
|
|
264
293
|
Store.multi do |multi|
|
265
|
-
(@start..@recurring['end']).step(
|
294
|
+
(@start..@recurring['end']).step(recurrence).each do |start_time|
|
266
295
|
@start = start_time
|
267
296
|
@stop = start_time + duration
|
268
297
|
|
269
298
|
if last_stop and @start < last_stop
|
270
299
|
@events.pop # Remove previously created event
|
271
|
-
raise ActivityOverlapError.new "Recurring activity overlap. Increase
|
300
|
+
raise ActivityOverlapError.new "Recurring activity overlap. Increase recurrence delta or decrease activity duration."
|
272
301
|
end
|
273
|
-
|
274
|
-
|
275
|
-
(@
|
276
|
-
|
277
|
-
|
302
|
+
unless overlap
|
303
|
+
existing.each do |value|
|
304
|
+
if (@start >= value['start'] and @start < value['stop']) ||
|
305
|
+
(@stop > value['start'] and @stop <= value['stop'])
|
306
|
+
@events.pop # Remove previously created event
|
307
|
+
raise ActivityOverlapError.new "activity overlaps existing at #{value['start']}"
|
308
|
+
end
|
278
309
|
end
|
279
310
|
end
|
280
311
|
multi.zadd(@primary_key, @start, JSON.generate(self.as_json(:allow_nan => true)))
|
@@ -284,9 +315,9 @@ module OpenC3
|
|
284
315
|
notify(kind: 'created')
|
285
316
|
else
|
286
317
|
validate_input(start: @start, stop: @stop, kind: @kind, data: @data)
|
287
|
-
|
318
|
+
unless overlap
|
288
319
|
# If we don't allow overlap we need to validate the time
|
289
|
-
collision = validate_time()
|
320
|
+
collision = validate_time(@start, @stop)
|
290
321
|
unless collision.nil?
|
291
322
|
raise ActivityOverlapError.new "activity overlaps existing at #{collision}"
|
292
323
|
end
|
@@ -308,20 +339,27 @@ module OpenC3
|
|
308
339
|
end
|
309
340
|
|
310
341
|
old_start = @start
|
311
|
-
|
312
|
-
|
313
|
-
if !overlap
|
342
|
+
old_uuid = @uuid
|
343
|
+
unless overlap
|
314
344
|
# If we don't allow overlap we need to validate the time
|
315
|
-
collision = validate_time(old_start)
|
345
|
+
collision = validate_time(start, stop, ignore_score: old_start)
|
316
346
|
unless collision.nil?
|
317
347
|
raise ActivityOverlapError.new "failed to update #{old_start}, no activities can overlap, collision: #{collision}"
|
318
348
|
end
|
319
349
|
end
|
350
|
+
set_input(start: start, stop: stop, kind: kind, data: data, events: @events)
|
351
|
+
@updated_at = Time.now.to_nsec_from_epoch
|
320
352
|
|
321
353
|
add_event(status: 'updated')
|
322
|
-
Store.
|
323
|
-
|
324
|
-
|
354
|
+
json = Store.zrangebyscore(@primary_key, old_start, old_start)
|
355
|
+
parsed = json.map { |value| JSON.parse(value, :allow_nan => true, :create_additions => true) }
|
356
|
+
parsed.each_with_index do |value, index|
|
357
|
+
if value['uuid'] == old_uuid
|
358
|
+
Store.multi do |multi|
|
359
|
+
multi.zrem(@primary_key, json[index])
|
360
|
+
multi.zadd(@primary_key, @start, JSON.generate(self.as_json(:allow_nan => true)))
|
361
|
+
end
|
362
|
+
end
|
325
363
|
end
|
326
364
|
notify(kind: 'updated', extra: old_start)
|
327
365
|
return @start
|
@@ -339,9 +377,16 @@ module OpenC3
|
|
339
377
|
event['message'] = message unless message.nil?
|
340
378
|
@fulfillment = fulfillment.nil? ? @fulfillment : fulfillment
|
341
379
|
@events << event
|
342
|
-
|
343
|
-
|
344
|
-
|
380
|
+
|
381
|
+
json = Store.zrangebyscore(@primary_key, @start, @start)
|
382
|
+
parsed = json.map { |value| JSON.parse(value, :allow_nan => true, :create_additions => true) }
|
383
|
+
parsed.each_with_index do |value, index|
|
384
|
+
if value['uuid'] == @uuid
|
385
|
+
Store.multi do |multi|
|
386
|
+
multi.zrem(@primary_key, json[index])
|
387
|
+
multi.zadd(@primary_key, @start, JSON.generate(self.as_json(:allow_nan => true)))
|
388
|
+
end
|
389
|
+
end
|
345
390
|
end
|
346
391
|
notify(kind: 'event')
|
347
392
|
end
|
@@ -356,24 +401,6 @@ module OpenC3
|
|
356
401
|
@events << event
|
357
402
|
end
|
358
403
|
|
359
|
-
# destroy the activity from the redis database
|
360
|
-
def destroy(recurring: false)
|
361
|
-
# Delete all recurring activities
|
362
|
-
if recurring and @recurring['end'] and @recurring['uuid']
|
363
|
-
uuid = @recurring['uuid']
|
364
|
-
array = Store.zrangebyscore("#{scope}#{PRIMARY_KEY}__#{@name}", @recurring['start'], @recurring['end'])
|
365
|
-
array.each do |value|
|
366
|
-
model = ActivityModel.from_json(value, name: @name, scope: @scope)
|
367
|
-
if model.recurring['uuid'] == uuid
|
368
|
-
Store.zremrangebyscore(@primary_key, model.start, model.start)
|
369
|
-
end
|
370
|
-
end
|
371
|
-
else
|
372
|
-
Store.zremrangebyscore(@primary_key, @start, @start)
|
373
|
-
end
|
374
|
-
notify(kind: 'deleted')
|
375
|
-
end
|
376
|
-
|
377
404
|
# update the redis stream / timeline topic that something has changed
|
378
405
|
def notify(kind:, extra: nil)
|
379
406
|
notification = {
|
@@ -395,12 +422,14 @@ module OpenC3
|
|
395
422
|
{
|
396
423
|
'name' => @name,
|
397
424
|
'updated_at' => @updated_at,
|
398
|
-
'fulfillment' => @fulfillment,
|
399
425
|
'start' => @start,
|
400
426
|
'stop' => @stop,
|
401
427
|
'kind' => @kind,
|
402
|
-
'events' => @events,
|
403
428
|
'data' => @data.as_json(*a),
|
429
|
+
'scope' => @scope,
|
430
|
+
'fulfillment' => @fulfillment,
|
431
|
+
'uuid' => @uuid,
|
432
|
+
'events' => @events,
|
404
433
|
'recurring' => @recurring.as_json(*a)
|
405
434
|
}
|
406
435
|
end
|
@@ -21,15 +21,22 @@
|
|
21
21
|
# if purchased from OpenC3, Inc.
|
22
22
|
|
23
23
|
require 'digest'
|
24
|
+
require 'securerandom'
|
24
25
|
require 'openc3/utilities/store'
|
25
26
|
|
26
27
|
module OpenC3
|
27
28
|
class AuthModel
|
28
29
|
PRIMARY_KEY = 'OPENC3__TOKEN'
|
30
|
+
SESSIONS_KEY = 'OPENC3__SESSIONS'
|
29
31
|
|
30
32
|
TOKEN_CACHE_TIMEOUT = 5
|
33
|
+
SESSION_CACHE_TIMEOUT = 5
|
31
34
|
@@token_cache = nil
|
32
35
|
@@token_cache_time = nil
|
36
|
+
@@session_cache = nil
|
37
|
+
@@session_cache_time = nil
|
38
|
+
|
39
|
+
MIN_TOKEN_LENGTH = 8
|
33
40
|
|
34
41
|
def self.set?(key = PRIMARY_KEY)
|
35
42
|
Store.exists(key) == 1
|
@@ -38,14 +45,23 @@ module OpenC3
|
|
38
45
|
def self.verify(token)
|
39
46
|
return false if token.nil? or token.empty?
|
40
47
|
|
48
|
+
time = Time.now
|
49
|
+
return true if @@session_cache and (time - @@session_cache_time) < SESSION_CACHE_TIMEOUT and @@session_cache[token]
|
41
50
|
token_hash = hash(token)
|
42
|
-
return true if @@token_cache and (
|
51
|
+
return true if @@token_cache and (time - @@token_cache_time) < TOKEN_CACHE_TIMEOUT and @@token_cache == token_hash
|
52
|
+
|
53
|
+
# Check sessions
|
54
|
+
@@session_cache = Store.hgetall(SESSIONS_KEY)
|
55
|
+
@@session_cache_time = time
|
56
|
+
return true if @@session_cache[token]
|
43
57
|
|
58
|
+
# Check Direct password
|
44
59
|
@@token_cache = Store.get(PRIMARY_KEY)
|
45
|
-
@@token_cache_time =
|
60
|
+
@@token_cache_time = time
|
46
61
|
return true if @@token_cache == token_hash
|
47
62
|
|
48
63
|
# Handle a service password - Generally only used by ScriptRunner
|
64
|
+
# TODO: Replace this with temporary service tokens
|
49
65
|
service_password = ENV['OPENC3_SERVICE_PASSWORD']
|
50
66
|
return true if service_password and service_password == token
|
51
67
|
|
@@ -54,6 +70,7 @@ module OpenC3
|
|
54
70
|
|
55
71
|
def self.set(token, old_token, key = PRIMARY_KEY)
|
56
72
|
raise "token must not be nil or empty" if token.nil? or token.empty?
|
73
|
+
raise "token must be at least 8 characters" if token.length < MIN_TOKEN_LENGTH
|
57
74
|
|
58
75
|
if set?(key)
|
59
76
|
raise "old_token must not be nil or empty" if old_token.nil? or old_token.empty?
|
@@ -62,6 +79,18 @@ module OpenC3
|
|
62
79
|
Store.set(key, hash(token))
|
63
80
|
end
|
64
81
|
|
82
|
+
def self.generate_session
|
83
|
+
token = SecureRandom.urlsafe_base64(nil, false)
|
84
|
+
Store.hset(SESSIONS_KEY, token, Time.now.iso8601)
|
85
|
+
return token
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.logout
|
89
|
+
Store.del(SESSIONS_KEY)
|
90
|
+
@@sessions_cache = nil
|
91
|
+
@@sessions_cache_time = nil
|
92
|
+
end
|
93
|
+
|
65
94
|
def self.hash(token)
|
66
95
|
Digest::SHA2.hexdigest token
|
67
96
|
end
|
@@ -100,15 +100,21 @@ module OpenC3
|
|
100
100
|
result, types = self._handle_item_override(target_name, packet_name, item_name, type: type, cache_timeout: cache_timeout, scope: scope)
|
101
101
|
return result if result
|
102
102
|
hash = get(target_name: target_name, packet_name: packet_name, cache_timeout: cache_timeout, scope: scope)
|
103
|
-
hash.values_at(*types).each do |
|
104
|
-
if
|
103
|
+
hash.values_at(*types).each do |cvt_value|
|
104
|
+
if cvt_value
|
105
105
|
if type == :FORMATTED or type == :WITH_UNITS
|
106
|
-
return
|
106
|
+
return cvt_value.to_s
|
107
107
|
end
|
108
|
-
return
|
108
|
+
return cvt_value
|
109
109
|
end
|
110
110
|
end
|
111
|
-
|
111
|
+
# RECEIVED_COUNT is a special case where it is 0 if it doesn't exist
|
112
|
+
# This allows scripts to check against the value to see if the packet was ever received
|
113
|
+
if item_name == "RECEIVED_COUNT"
|
114
|
+
return 0
|
115
|
+
else
|
116
|
+
return nil
|
117
|
+
end
|
112
118
|
end
|
113
119
|
|
114
120
|
# Return all item values and limit state from the CVT
|
@@ -90,16 +90,16 @@ module OpenC3
|
|
90
90
|
Gem.sources = [rubygems_url] if rubygems_url
|
91
91
|
Gem.done_installing_hooks.clear
|
92
92
|
begin
|
93
|
-
# Look for local gems only first, this avoids
|
93
|
+
# Look for local gems only first, this avoids lengthy timeouts when checking rubygems in airgap env
|
94
94
|
Gem.install(gem_file_path, "> 0.pre", build_args: ['--no-document'], prerelease: true, domain: :local)
|
95
|
-
rescue Gem::Exception =>
|
95
|
+
rescue Gem::Exception => e
|
96
96
|
# If there is a failure look for both local and remote gems
|
97
97
|
Gem.install(gem_file_path, "> 0.pre", build_args: ['--no-document'], prerelease: true, domain: :both)
|
98
98
|
end
|
99
|
-
rescue =>
|
100
|
-
message = "Gem file #{gem_file_path} error installing to #{ENV['GEM_HOME']}\n#{
|
99
|
+
rescue => e
|
100
|
+
message = "Gem file #{gem_file_path} error installing to #{ENV['GEM_HOME']}\n#{e.formatted}"
|
101
101
|
Logger.error message
|
102
|
-
raise
|
102
|
+
raise e
|
103
103
|
end
|
104
104
|
|
105
105
|
def self.destroy(name, log_and_raise_needed_errors: true)
|
@@ -114,9 +114,9 @@ module OpenC3
|
|
114
114
|
else
|
115
115
|
begin
|
116
116
|
Gem::Uninstaller.new(gem_name, {:version => version, :force => true}).uninstall
|
117
|
-
rescue =>
|
118
|
-
Logger.error "Gem file #{name} error uninstalling\n#{
|
119
|
-
raise
|
117
|
+
rescue => e
|
118
|
+
Logger.error "Gem file #{name} error uninstalling\n#{e.formatted}"
|
119
|
+
raise e
|
120
120
|
end
|
121
121
|
end
|
122
122
|
end
|
@@ -69,7 +69,7 @@ module OpenC3
|
|
69
69
|
end
|
70
70
|
|
71
71
|
# Called by the PluginsController to parse the plugin variables
|
72
|
-
# Doesn't
|
72
|
+
# Doesn't actually create the plugin during the phase
|
73
73
|
def self.install_phase1(gem_file_path, existing_variables: nil, existing_plugin_txt_lines: nil, process_existing: false, scope:, validate_only: false)
|
74
74
|
gem_name = File.basename(gem_file_path).split("__")[0]
|
75
75
|
|
@@ -140,8 +140,8 @@ module OpenC3
|
|
140
140
|
end
|
141
141
|
|
142
142
|
# Called by the PluginsController to create the plugin
|
143
|
-
# Because this uses ERB it must be run in a
|
144
|
-
# prevent corruption and single require problems in the current
|
143
|
+
# Because this uses ERB it must be run in a separate process from the API to
|
144
|
+
# prevent corruption and single require problems in the current process
|
145
145
|
def self.install_phase2(plugin_hash, scope:, gem_file_path: nil, validate_only: false)
|
146
146
|
# Register plugin to aid in uninstall if install fails
|
147
147
|
plugin_hash.delete("existing_plugin_txt_lines")
|
@@ -17,14 +17,14 @@
|
|
17
17
|
# All changes Copyright 2022, OpenC3, Inc.
|
18
18
|
# All Rights Reserved
|
19
19
|
#
|
20
|
-
# This file may also be used under the terms of a commercial license
|
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
|
require 'openc3/utilities/store'
|
24
24
|
|
25
25
|
module OpenC3
|
26
26
|
# Tracks the files which are being stored in buckets for data reduction purposes.
|
27
|
-
# Files are stored in a Redis set by
|
27
|
+
# Files are stored in a Redis set by splitting their filenames and storing in
|
28
28
|
# a set named SCOPE__TARGET__reducer__TYPE, e.g. DEFAULT__INST__reducer__decom
|
29
29
|
# Where TYPE can be 'decom', 'minute', or 'hour'. 'day' is not necessary because
|
30
30
|
# day is the final reduction state. As files are reduced they are removed from
|
@@ -110,7 +110,7 @@ module OpenC3
|
|
110
110
|
end
|
111
111
|
|
112
112
|
def create(update: false, force: false, queued: false)
|
113
|
-
# Ensure there are no "." in the scope name - prevents gems
|
113
|
+
# Ensure there are no "." in the scope name - prevents gems accidentally becoming scope names
|
114
114
|
raise "Invalid scope name: #{@name}" if @name !~ /^[a-zA-Z0-9_-]+$/
|
115
115
|
@name = @name.upcase
|
116
116
|
@scope = @name # Ensure @scope matches @name
|
@@ -34,15 +34,15 @@ module OpenC3
|
|
34
34
|
class SortedOverlapError < SortedError; end
|
35
35
|
|
36
36
|
class SortedModel < Model
|
37
|
-
SORTED_TYPE = 'sorted'.freeze # To be
|
38
|
-
PRIMARY_KEY = '__SORTED'.freeze # To be
|
37
|
+
SORTED_TYPE = 'sorted'.freeze # To be overridden by base class
|
38
|
+
PRIMARY_KEY = '__SORTED'.freeze # To be overridden by base class
|
39
39
|
|
40
|
-
# MUST be
|
40
|
+
# MUST be overridden by any subclasses
|
41
41
|
def self.pk(scope)
|
42
42
|
return "#{scope}#{PRIMARY_KEY}"
|
43
43
|
end
|
44
44
|
|
45
|
-
# MUST be
|
45
|
+
# MUST be overridden by any subclasses
|
46
46
|
def self.notify(scope:, kind:, start:, stop: nil)
|
47
47
|
# Do nothing by default
|
48
48
|
end
|
@@ -109,7 +109,7 @@ module OpenC3
|
|
109
109
|
modified_targets = Bucket.getClient().list_files(bucket: ENV['OPENC3_CONFIG_BUCKET'], path: "DEFAULT/targets_modified/", only_directories: true)
|
110
110
|
modified_targets.each do |target_name|
|
111
111
|
# A target could have been deleted without removing the modified files
|
112
|
-
# Thus we have to check for the
|
112
|
+
# Thus we have to check for the existence of the target_name key
|
113
113
|
if targets.has_key?(target_name)
|
114
114
|
targets[target_name]['modified'] = true
|
115
115
|
end
|
@@ -250,7 +250,7 @@ module OpenC3
|
|
250
250
|
(items - found_items).each do |item|
|
251
251
|
not_found << "'#{target_name} #{packet_name} #{item}'"
|
252
252
|
end
|
253
|
-
# 'does not exist' not
|
253
|
+
# 'does not exist' not grammatically correct but we use it in every other exception
|
254
254
|
raise "Item(s) #{not_found.join(', ')} does not exist"
|
255
255
|
end
|
256
256
|
found
|
@@ -1121,7 +1121,7 @@ module OpenC3
|
|
1121
1121
|
end
|
1122
1122
|
end
|
1123
1123
|
# If there are any topics (packets) left over that haven't been
|
1124
|
-
#
|
1124
|
+
# explicitly handled above, spawn another microservice
|
1125
1125
|
if all_topics.length > 0
|
1126
1126
|
instance = nil
|
1127
1127
|
instance = deploy_count unless deploy_count == 0
|
@@ -25,7 +25,7 @@ require 'openc3/utilities/local_mode'
|
|
25
25
|
module OpenC3
|
26
26
|
class ToolConfigModel
|
27
27
|
def self.config_tool_names(scope: $openc3_scope)
|
28
|
-
|
28
|
+
_, keys = Store.scan(0, match: "#{scope}__config__*", type: 'hash', count: 100)
|
29
29
|
# Just return the tool name that is used in the other APIs
|
30
30
|
return keys.map! { |key| key.split('__')[2] }.sort
|
31
31
|
end
|
@@ -96,7 +96,7 @@ module OpenC3
|
|
96
96
|
def self.set_position(name:, position:, scope:)
|
97
97
|
moving = from_json(get(name: name, scope: scope), scope: scope)
|
98
98
|
old_pos = moving.position
|
99
|
-
new_pos =
|
99
|
+
new_pos = position
|
100
100
|
direction = :down
|
101
101
|
if (old_pos == new_pos)
|
102
102
|
return # we're not doing anything
|
@@ -225,7 +225,7 @@ module OpenC3
|
|
225
225
|
@shown = ConfigParser.handle_true_false(parameters[0])
|
226
226
|
when 'POSITION'
|
227
227
|
parser.verify_num_parameters(1, 1, "POSITION <value>")
|
228
|
-
@position = parameters[0].
|
228
|
+
@position = parameters[0].to_f
|
229
229
|
when 'DISABLE_ERB'
|
230
230
|
# 0 to unlimited parameters
|
231
231
|
@disable_erb ||= []
|
@@ -274,8 +274,8 @@ module OpenC3
|
|
274
274
|
ConfigTopic.write({ kind: 'deleted', type: 'tool', name: @folder_name, plugin: @plugin }, scope: @scope)
|
275
275
|
end
|
276
276
|
end
|
277
|
-
rescue Exception =>
|
278
|
-
Logger.error("Error undeploying tool model #{@name} in scope #{@scope} due to #{
|
277
|
+
rescue Exception => e
|
278
|
+
Logger.error("Error undeploying tool model #{@name} in scope #{@scope} due to #{e}")
|
279
279
|
end
|
280
280
|
|
281
281
|
##################################################
|