openc3 5.18.0 → 5.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/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
|
##################################################
|