openc3 7.0.0.pre.rc3 → 7.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.
- checksums.yaml +4 -4
- data/data/config/interface_modifiers.yaml +1 -1
- data/data/config/item_modifiers.yaml +18 -6
- data/data/config/telemetry.yaml +1 -1
- data/lib/openc3/accessors/json_accessor.rb +1 -1
- data/lib/openc3/api/tlm_api.rb +3 -3
- data/lib/openc3/config/config_parser.rb +4 -4
- data/lib/openc3/conversions/conversion.rb +3 -3
- data/lib/openc3/core_ext/faraday.rb +4 -0
- data/lib/openc3/logs/log_writer.rb +24 -6
- data/lib/openc3/logs/packet_log_writer.rb +1 -4
- data/lib/openc3/logs/stream_log_pair.rb +11 -4
- data/lib/openc3/logs/text_log_writer.rb +1 -4
- data/lib/openc3/microservices/interface_microservice.rb +8 -2
- data/lib/openc3/microservices/log_microservice.rb +7 -2
- data/lib/openc3/microservices/microservice.rb +10 -4
- data/lib/openc3/microservices/queue_microservice.rb +3 -0
- data/lib/openc3/microservices/scope_cleanup_microservice.rb +116 -1
- data/lib/openc3/microservices/text_log_microservice.rb +4 -1
- data/lib/openc3/migrations/20260204000000_remove_decom_reducer.rb +2 -0
- data/lib/openc3/models/activity_model.rb +15 -3
- data/lib/openc3/models/cvt_model.rb +2 -247
- data/lib/openc3/models/plugin_store_model.rb +1 -1
- data/lib/openc3/models/script_engine_model.rb +1 -1
- data/lib/openc3/models/target_model.rb +32 -34
- data/lib/openc3/models/tool_model.rb +18 -5
- data/lib/openc3/models/trigger_model.rb +1 -1
- data/lib/openc3/models/widget_model.rb +1 -2
- data/lib/openc3/operators/operator.rb +9 -7
- data/lib/openc3/packets/json_packet.rb +2 -0
- data/lib/openc3/packets/packet.rb +1 -0
- data/lib/openc3/packets/packet_config.rb +28 -12
- data/lib/openc3/script/calendar.rb +8 -0
- data/lib/openc3/script/script.rb +19 -0
- data/lib/openc3/script/storage.rb +6 -6
- data/lib/openc3/system/system.rb +6 -6
- data/lib/openc3/tools/cmd_tlm_server/interface_thread.rb +0 -2
- data/lib/openc3/top_level.rb +15 -63
- data/lib/openc3/topics/limits_event_topic.rb +1 -1
- data/lib/openc3/utilities/bucket_utilities.rb +3 -1
- data/lib/openc3/utilities/cli_generator.rb +7 -0
- data/lib/openc3/utilities/cmd_log.rb +1 -1
- data/lib/openc3/utilities/local_mode.rb +3 -0
- data/lib/openc3/utilities/process_manager.rb +1 -1
- data/lib/openc3/utilities/python_proxy.rb +11 -4
- data/lib/openc3/utilities/questdb_client.rb +735 -19
- data/lib/openc3/utilities/running_script.rb +25 -7
- data/lib/openc3/utilities/script.rb +452 -0
- data/lib/openc3/utilities/secrets.rb +1 -1
- data/lib/openc3/version.rb +5 -5
- data/templates/conversion/conversion.py +0 -8
- data/templates/conversion/conversion.rb +0 -11
- data/templates/tool_angular/package.json +2 -2
- data/templates/tool_react/package.json +1 -1
- data/templates/tool_svelte/package.json +1 -1
- data/templates/tool_vue/package.json +3 -3
- data/templates/widget/package.json +2 -2
- metadata +16 -2
- data/lib/openc3/migrations/20251022000000_remove_unique_id.rb +0 -23
|
@@ -24,6 +24,7 @@ require 'openc3/io/stdout'
|
|
|
24
24
|
require 'openc3/io/stderr'
|
|
25
25
|
require 'childprocess'
|
|
26
26
|
require 'openc3/script/suite_runner'
|
|
27
|
+
require 'openc3/utilities/script'
|
|
27
28
|
require 'openc3/utilities/store'
|
|
28
29
|
require 'openc3/utilities/store_queued'
|
|
29
30
|
require 'openc3/utilities/bucket_require'
|
|
@@ -31,6 +32,7 @@ require 'openc3/models/offline_access_model'
|
|
|
31
32
|
require 'openc3/models/environment_model'
|
|
32
33
|
require 'openc3/models/script_engine_model'
|
|
33
34
|
require 'openc3/models/script_status_model'
|
|
35
|
+
require 'fileutils'
|
|
34
36
|
|
|
35
37
|
if not defined? RAILS_ROOT
|
|
36
38
|
if ENV['RAILS_ROOT']
|
|
@@ -60,8 +62,8 @@ module OpenC3
|
|
|
60
62
|
private
|
|
61
63
|
# Define all the user input methods used in scripting which we need to broadcast to the frontend
|
|
62
64
|
# Note: This list matches the list in run_script.rb:116
|
|
63
|
-
SCRIPT_METHODS = %i[ask ask_string message_box vertical_message_box combo_box prompt prompt_for_hazardous
|
|
64
|
-
prompt_for_critical_cmd metadata_input open_file_dialog open_files_dialog]
|
|
65
|
+
SCRIPT_METHODS = %i[ask ask_string message_box vertical_message_box combo_box check_box prompt prompt_for_hazardous
|
|
66
|
+
prompt_for_critical_cmd metadata_input open_file_dialog open_files_dialog open_bucket_dialog]
|
|
65
67
|
SCRIPT_METHODS.each do |method|
|
|
66
68
|
define_method(method) do |*args, **kwargs|
|
|
67
69
|
while true
|
|
@@ -72,10 +74,21 @@ module OpenC3
|
|
|
72
74
|
input = RunningScript.instance.user_input
|
|
73
75
|
# All ask and prompt dialogs should include a 'Cancel' button
|
|
74
76
|
# If they cancel we wait so they can potentially stop
|
|
75
|
-
if input == '
|
|
77
|
+
if input == 'COSMOS__CANCEL'
|
|
76
78
|
RunningScript.instance.perform_pause
|
|
77
79
|
else
|
|
78
|
-
if
|
|
80
|
+
if method.to_s == 'open_bucket_dialog'
|
|
81
|
+
bucket = input['bucket']
|
|
82
|
+
path = input['path']
|
|
83
|
+
filename = input['filename']
|
|
84
|
+
# Path from bucket dialog already includes scope prefix (e.g. DEFAULT/targets/...)
|
|
85
|
+
# _get_storage_file prepends scope, so strip it from the path to avoid doubling
|
|
86
|
+
scope = RunningScript.instance.scope
|
|
87
|
+
path = path.sub(/\A#{Regexp.escape(scope)}\//, '')
|
|
88
|
+
file = _get_storage_file(path, bucket: bucket, scope: scope)
|
|
89
|
+
file.filename = filename
|
|
90
|
+
return file
|
|
91
|
+
elsif (method.to_s.include?('open_file'))
|
|
79
92
|
files = input.map do |filename|
|
|
80
93
|
file = _get_storage_file("tmp/#{filename}", scope: RunningScript.instance.scope)
|
|
81
94
|
# Set filename method we added to Tempfile in the core_ext
|
|
@@ -377,7 +390,9 @@ class RunningScript
|
|
|
377
390
|
scope = $openc3_scope
|
|
378
391
|
tags = []
|
|
379
392
|
end
|
|
380
|
-
|
|
393
|
+
log_dir = File.join(RAILS_ROOT, 'tmp', 'script_logs')
|
|
394
|
+
FileUtils.mkdir_p(log_dir)
|
|
395
|
+
@@message_log = OpenC3::MessageLog.new("sr", log_dir, tags: tags, scope: scope)
|
|
381
396
|
end
|
|
382
397
|
|
|
383
398
|
def message_log
|
|
@@ -422,7 +437,9 @@ class RunningScript
|
|
|
422
437
|
|
|
423
438
|
process = ChildProcess.build(process_name, runner_path.to_s, running_script_id.to_s, scope)
|
|
424
439
|
process.io.inherit! # Helps with debugging
|
|
425
|
-
|
|
440
|
+
script_cwd = File.join(RAILS_ROOT, 'tmp', "script_#{running_script_id}")
|
|
441
|
+
FileUtils.mkdir_p(script_cwd)
|
|
442
|
+
process.cwd = script_cwd
|
|
426
443
|
|
|
427
444
|
# Check for offline access token
|
|
428
445
|
model = nil
|
|
@@ -1213,7 +1230,8 @@ class RunningScript
|
|
|
1213
1230
|
end
|
|
1214
1231
|
@suite_report = OpenC3::SuiteRunner.suite_results.report
|
|
1215
1232
|
# Write out the report to a local file
|
|
1216
|
-
log_dir = File.join(RAILS_ROOT, '
|
|
1233
|
+
log_dir = File.join(RAILS_ROOT, 'tmp', 'script_logs')
|
|
1234
|
+
FileUtils.mkdir_p(log_dir)
|
|
1217
1235
|
filename = File.join(log_dir, File.build_timestamped_filename(['sr', parts.join('__')]))
|
|
1218
1236
|
File.open(filename, 'wb') do |file|
|
|
1219
1237
|
file.write(OpenC3::SuiteRunner.suite_results.report)
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
3
|
+
# Copyright 2022 Ball Aerospace & Technologies Corp.
|
|
4
|
+
# All Rights Reserved.
|
|
5
|
+
#
|
|
6
|
+
# This program is distributed in the hope that it will be useful,
|
|
7
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
8
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
9
|
+
# See LICENSE.md for more details.
|
|
10
|
+
|
|
11
|
+
# Modified by OpenC3, Inc.
|
|
12
|
+
# All changes Copyright 2026, OpenC3, Inc.
|
|
13
|
+
# All Rights Reserved
|
|
14
|
+
#
|
|
15
|
+
# This file may also be used under the terms of a commercial license
|
|
16
|
+
# if purchased from OpenC3, Inc.
|
|
17
|
+
|
|
18
|
+
require 'tempfile'
|
|
19
|
+
require 'openc3/utilities/target_file'
|
|
20
|
+
require 'openc3/utilities/running_script'
|
|
21
|
+
require 'openc3/script/suite'
|
|
22
|
+
require 'openc3/script/suite_runner'
|
|
23
|
+
require 'openc3/tools/test_runner/test'
|
|
24
|
+
|
|
25
|
+
OpenC3.require_file 'openc3/utilities/store'
|
|
26
|
+
|
|
27
|
+
class Script < OpenC3::TargetFile
|
|
28
|
+
def self.all(scope, target = nil)
|
|
29
|
+
super(scope, nil, target: target) # No path matchers
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.lock(scope, name, username)
|
|
33
|
+
name = name.split('*')[0] # Split '*' that indicates modified
|
|
34
|
+
OpenC3::Store.hset("#{scope}__script-locks", name, username)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.unlock(scope, name)
|
|
38
|
+
name = name.split('*')[0] # Split '*' that indicates modified
|
|
39
|
+
OpenC3::Store.hdel("#{scope}__script-locks", name)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.locked?(scope, name)
|
|
43
|
+
name = name.split('*')[0] # Split '*' that indicates modified
|
|
44
|
+
locked_by = OpenC3::Store.hget("#{scope}__script-locks", name)
|
|
45
|
+
locked_by ||= false
|
|
46
|
+
locked_by
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.get_breakpoints(scope, name)
|
|
50
|
+
breakpoints = OpenC3::Store.hget("#{scope}__script-breakpoints", name.split('*')[0]) # Split '*' that indicates modified
|
|
51
|
+
return JSON.parse(breakpoints, allow_nan: true, create_additions: true) if breakpoints
|
|
52
|
+
[]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.process_suite(name, contents, new_process: true, username: nil, scope:)
|
|
56
|
+
python = false
|
|
57
|
+
python = true if File.extname(name) == '.py'
|
|
58
|
+
|
|
59
|
+
start = Time.now
|
|
60
|
+
|
|
61
|
+
if python
|
|
62
|
+
temp = Tempfile.new(%w[suite .py])
|
|
63
|
+
else
|
|
64
|
+
temp = Tempfile.new(%w[suite .rb])
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Remove any carriage returns which ruby doesn't like
|
|
68
|
+
temp.write(contents.gsub(/\r/, ' '))
|
|
69
|
+
temp.close
|
|
70
|
+
|
|
71
|
+
# We open a new process so as to not pollute the API with require
|
|
72
|
+
success = true
|
|
73
|
+
if new_process or python
|
|
74
|
+
if python
|
|
75
|
+
runner_path = File.join(RAILS_ROOT, 'scripts', 'run_suite_analysis.py')
|
|
76
|
+
process_name = ENV['OPENC3_PYTHON_BIN'] || '/openc3/python/.venv/bin/python'
|
|
77
|
+
process = ChildProcess.build(process_name, runner_path.to_s, scope, temp.path)
|
|
78
|
+
else
|
|
79
|
+
runner_path = File.join(RAILS_ROOT, 'scripts', 'run_suite_analysis.rb')
|
|
80
|
+
process = ChildProcess.build('ruby', runner_path.to_s, scope, temp.path)
|
|
81
|
+
end
|
|
82
|
+
process.cwd = File.join(RAILS_ROOT, 'scripts')
|
|
83
|
+
|
|
84
|
+
# Check for offline access token
|
|
85
|
+
model = nil
|
|
86
|
+
model = OpenC3::OfflineAccessModel.get_model(name: username, scope: scope) if username and username != ''
|
|
87
|
+
|
|
88
|
+
# Set proper secrets for running script
|
|
89
|
+
process.environment['SECRET_KEY_BASE'] = nil
|
|
90
|
+
process.environment['OPENC3_REDIS_USERNAME'] = ENV['OPENC3_SR_REDIS_USERNAME'] || 'OPENC3_SR_REDIS_USERNAME not set'
|
|
91
|
+
process.environment['OPENC3_REDIS_PASSWORD'] = ENV['OPENC3_SR_REDIS_PASSWORD'] || 'OPENC3_SR_REDIS_PASSWORD not set'
|
|
92
|
+
process.environment['OPENC3_BUCKET_USERNAME'] = ENV['OPENC3_SR_BUCKET_USERNAME'] || 'OPENC3_SR_BUCKET_USERNAME not set'
|
|
93
|
+
process.environment['OPENC3_BUCKET_PASSWORD'] = ENV['OPENC3_SR_BUCKET_PASSWORD'] || 'OPENC3_SR_BUCKET_PASSWORD not set'
|
|
94
|
+
process.environment['OPENC3_SR_REDIS_USERNAME'] = nil
|
|
95
|
+
process.environment['OPENC3_SR_REDIS_PASSWORD'] = nil
|
|
96
|
+
process.environment['OPENC3_SR_BUCKET_USERNAME'] = nil
|
|
97
|
+
process.environment['OPENC3_SR_BUCKET_PASSWORD'] = nil
|
|
98
|
+
process.environment['OPENC3_API_CLIENT'] = ENV['OPENC3_API_CLIENT'] || 'api'
|
|
99
|
+
if model and model.offline_access_token
|
|
100
|
+
auth = OpenC3::OpenC3KeycloakAuthentication.new(ENV['OPENC3_KEYCLOAK_URL'])
|
|
101
|
+
valid_token = auth.get_token_from_refresh_token(model.offline_access_token)
|
|
102
|
+
if valid_token
|
|
103
|
+
process.environment['OPENC3_API_TOKEN'] = model.offline_access_token
|
|
104
|
+
else
|
|
105
|
+
model.offline_access_token = nil
|
|
106
|
+
model.update
|
|
107
|
+
raise "offline_access token invalid for script"
|
|
108
|
+
end
|
|
109
|
+
else
|
|
110
|
+
process.environment['OPENC3_API_USER'] = ENV['OPENC3_API_USER'] || nil
|
|
111
|
+
if ENV['OPENC3_SERVICE_PASSWORD']
|
|
112
|
+
process.environment['OPENC3_API_PASSWORD'] = ENV['OPENC3_SERVICE_PASSWORD']
|
|
113
|
+
else
|
|
114
|
+
# The viewer user doesn't have an offline access token (because they can't run scripts)
|
|
115
|
+
# but they still want to be able to view suite files
|
|
116
|
+
# Since processing a suite file requires running it they won't get the Suite chrome
|
|
117
|
+
# so return nothing here and allow Script Runner to simply view the suite file
|
|
118
|
+
return '', '', false
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
process.environment['GEM_HOME'] = ENV['GEM_HOME'] || '/gems'
|
|
122
|
+
process.environment['PYTHONUSERBASE'] = ENV['PYTHONUSERBASE'] || '/gems/python_packages'
|
|
123
|
+
# Preserve PYTHONPATH to ensure Python can find both UV venv and user packages
|
|
124
|
+
process.environment['PYTHONPATH'] = ENV['PYTHONPATH'] || '.'
|
|
125
|
+
|
|
126
|
+
# Spawned process should not be controlled by same Bundler constraints as spawning process
|
|
127
|
+
ENV.each do |key, _value|
|
|
128
|
+
if key =~ /^BUNDLE/
|
|
129
|
+
process.environment[key] = nil
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
process.environment['RUBYOPT'] = nil # Removes loading bundler setup
|
|
133
|
+
process.environment['OPENC3_SCOPE'] = scope
|
|
134
|
+
|
|
135
|
+
stdout = Tempfile.new("child-stdout")
|
|
136
|
+
stdout.sync = true
|
|
137
|
+
stderr = Tempfile.new("child-stderr")
|
|
138
|
+
stderr.sync = true
|
|
139
|
+
process.io.stdout = stdout
|
|
140
|
+
process.io.stderr = stderr
|
|
141
|
+
process.start
|
|
142
|
+
process.wait
|
|
143
|
+
stdout.rewind
|
|
144
|
+
stdout_results = stdout.read
|
|
145
|
+
stdout.close
|
|
146
|
+
stdout.unlink
|
|
147
|
+
stderr.rewind
|
|
148
|
+
stderr_results = stderr.read
|
|
149
|
+
stderr.close
|
|
150
|
+
stderr.unlink
|
|
151
|
+
success = process.exit_code == 0
|
|
152
|
+
else
|
|
153
|
+
require temp.path
|
|
154
|
+
stdout_results = OpenC3::SuiteRunner.build_suites.as_json().to_json(allow_nan: true)
|
|
155
|
+
end
|
|
156
|
+
temp.delete
|
|
157
|
+
puts "Processed #{name} in #{Time.now - start} seconds"
|
|
158
|
+
# Make sure we're getting the last line which should be the suite
|
|
159
|
+
puts "Stdout Results:#{stdout_results}:"
|
|
160
|
+
puts "Stderr Results:#{stderr_results}:"
|
|
161
|
+
stdout_results = stdout_results.split("\n")[-1] if stdout_results
|
|
162
|
+
return stdout_results, stderr_results, success
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def self.create(params)
|
|
166
|
+
existing = body(params[:scope], params[:name])
|
|
167
|
+
# Commit if there is no existing or something has changed
|
|
168
|
+
if existing.nil? or existing != params[:text]
|
|
169
|
+
super(params[:scope], params[:name], params[:text])
|
|
170
|
+
end
|
|
171
|
+
breakpoints = params[:breakpoints]
|
|
172
|
+
if breakpoints
|
|
173
|
+
if breakpoints.empty?
|
|
174
|
+
OpenC3::Store.hdel("#{params[:scope]}__script-breakpoints", params[:name])
|
|
175
|
+
else
|
|
176
|
+
OpenC3::Store.hset("#{params[:scope]}__script-breakpoints", params[:name],
|
|
177
|
+
breakpoints.as_json().to_json(allow_nan: true))
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def self.delete_temp(scope)
|
|
183
|
+
files = super(scope)
|
|
184
|
+
files.each do |name|
|
|
185
|
+
# Remove any breakpoints associated with the temp files
|
|
186
|
+
OpenC3::Store.hdel("#{scope}__script-breakpoints", "#{TEMP_FOLDER}/#{File.basename(name)}")
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def self.destroy(scope, name)
|
|
191
|
+
super(scope, name)
|
|
192
|
+
OpenC3::Store.hdel("#{scope}__script-breakpoints", name)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def self.run(
|
|
196
|
+
scope,
|
|
197
|
+
name,
|
|
198
|
+
suite_runner = nil,
|
|
199
|
+
disconnect = false,
|
|
200
|
+
environment = nil,
|
|
201
|
+
user_full_name = nil,
|
|
202
|
+
username = nil,
|
|
203
|
+
line_no = nil,
|
|
204
|
+
end_line_no = nil
|
|
205
|
+
)
|
|
206
|
+
RunningScript.spawn(scope, name, suite_runner, disconnect, environment, user_full_name, username, line_no, end_line_no)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def self.instrumented(filename, text)
|
|
210
|
+
language = detect_language(text, filename)
|
|
211
|
+
if language == 'ruby'
|
|
212
|
+
return {
|
|
213
|
+
'title' => 'Instrumented Script',
|
|
214
|
+
'description' =>
|
|
215
|
+
RunningScript.instrument_script(
|
|
216
|
+
text,
|
|
217
|
+
filename,
|
|
218
|
+
true,
|
|
219
|
+
).split("\n").as_json().to_json(allow_nan: true),
|
|
220
|
+
}
|
|
221
|
+
elsif language == 'python'
|
|
222
|
+
start = Time.now
|
|
223
|
+
temp = Tempfile.new(%w[instrument .py])
|
|
224
|
+
temp.write(text)
|
|
225
|
+
temp.close
|
|
226
|
+
|
|
227
|
+
runner_path = File.join(RAILS_ROOT, 'scripts', 'run_instrument.py')
|
|
228
|
+
process_name = ENV['OPENC3_PYTHON_BIN'] || '/openc3/python/.venv/bin/python'
|
|
229
|
+
process = ChildProcess.build(process_name, runner_path.to_s, temp.path)
|
|
230
|
+
process.cwd = File.join(RAILS_ROOT, 'scripts')
|
|
231
|
+
|
|
232
|
+
stdout = Tempfile.new("child-stdout")
|
|
233
|
+
stdout.sync = true
|
|
234
|
+
stderr = Tempfile.new("child-stderr")
|
|
235
|
+
stderr.sync = true
|
|
236
|
+
process.io.stdout = stdout
|
|
237
|
+
process.io.stderr = stderr
|
|
238
|
+
process.start
|
|
239
|
+
process.wait
|
|
240
|
+
stdout.rewind
|
|
241
|
+
stdout_results = stdout.read
|
|
242
|
+
stdout.close
|
|
243
|
+
stdout.unlink
|
|
244
|
+
stderr.rewind
|
|
245
|
+
stderr_results = stderr.read
|
|
246
|
+
stderr.close
|
|
247
|
+
stderr.unlink
|
|
248
|
+
success = process.exit_code == 0
|
|
249
|
+
puts "Processed Instrumenting #{filename} in #{Time.now - start} seconds"
|
|
250
|
+
# Make sure we're getting the last line which should be the suite
|
|
251
|
+
puts "Stdout Results:#{stdout_results}:"
|
|
252
|
+
puts "Stderr Results:#{stderr_results}:"
|
|
253
|
+
# stdout_results = stdout_results.split("\n")[-1] if stdout_results
|
|
254
|
+
|
|
255
|
+
if success
|
|
256
|
+
return {
|
|
257
|
+
'title' => 'Instrumented Script',
|
|
258
|
+
'description' =>
|
|
259
|
+
stdout_results.to_s.split("\n").as_json().to_json(allow_nan: true),
|
|
260
|
+
}
|
|
261
|
+
else
|
|
262
|
+
return {
|
|
263
|
+
'title' => 'Error Instrumenting Script',
|
|
264
|
+
'description' =>
|
|
265
|
+
(stdout_results.to_s + stderr_results.to_s).split("\n").as_json().to_json(allow_nan: true),
|
|
266
|
+
}
|
|
267
|
+
end
|
|
268
|
+
else
|
|
269
|
+
return {
|
|
270
|
+
'title' => 'Instrumenting Not Supported',
|
|
271
|
+
'description' => ['Only Ruby and Python Support Viewing Instrumentation'].as_json.to_json,
|
|
272
|
+
}
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def self.detect_language(text, filename = nil)
|
|
277
|
+
if filename
|
|
278
|
+
extension = File.extname(filename)
|
|
279
|
+
if extension == '.rb'
|
|
280
|
+
return 'ruby'
|
|
281
|
+
elsif extension == '.py'
|
|
282
|
+
return 'python'
|
|
283
|
+
elsif extension.length > 0
|
|
284
|
+
return 'other'
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
return 'ruby' if text =~ /^\s*(require|load|puts) /
|
|
289
|
+
return 'python' if text =~ /^\s*(import|from) /
|
|
290
|
+
return 'ruby' if text =~ /^\s*end\s*$/
|
|
291
|
+
return 'python' if text =~ /^\s*(if|def|while|else|elif|class).*:\s*$/
|
|
292
|
+
return 'ruby' # otherwise guess Ruby
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def self.mnemonics(filename, text)
|
|
296
|
+
# Ruby and Python are currently handled in-browser
|
|
297
|
+
# Script Engine Possibly
|
|
298
|
+
extension = File.extname(filename).to_s.downcase
|
|
299
|
+
script_engine_model = OpenC3::ScriptEngineModel.get_model(name: extension, scope: 'DEFAULT')
|
|
300
|
+
if script_engine_model
|
|
301
|
+
script_engine = script_engine_model.filename
|
|
302
|
+
if File.extname(script_engine).to_s.downcase == '.py'
|
|
303
|
+
process_name = ENV['OPENC3_PYTHON_BIN'] || '/openc3/python/.venv/bin/python'
|
|
304
|
+
runner_path = File.join(RAILS_ROOT, 'scripts', 'script_engine_cmd.py')
|
|
305
|
+
else
|
|
306
|
+
process_name = 'ruby'
|
|
307
|
+
runner_path = File.join(RAILS_ROOT, 'scripts', 'script_engine_cmd.rb')
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
tf = nil
|
|
311
|
+
begin
|
|
312
|
+
tf = Tempfile.new((['mnemonics', extension]))
|
|
313
|
+
tf.write(text)
|
|
314
|
+
tf.close()
|
|
315
|
+
results, status = Open3.capture2e("#{process_name} \"#{runner_path}\" mnemonic_check \"#{tf.path}\"")
|
|
316
|
+
lines = []
|
|
317
|
+
if results and results.length > 0
|
|
318
|
+
results.each_line do |line|
|
|
319
|
+
lines << line
|
|
320
|
+
end
|
|
321
|
+
return(
|
|
322
|
+
{ 'title' => 'Mnemonics Check Failed', 'description' => lines.as_json().to_json(allow_nan: true) }
|
|
323
|
+
)
|
|
324
|
+
else
|
|
325
|
+
return(
|
|
326
|
+
{
|
|
327
|
+
'title' => 'Mnemonics Check Successful',
|
|
328
|
+
'description' => ["Mnemonics OK"].as_json().to_json(allow_nan: true),
|
|
329
|
+
}
|
|
330
|
+
)
|
|
331
|
+
end
|
|
332
|
+
ensure
|
|
333
|
+
tf.unlink if tf
|
|
334
|
+
end
|
|
335
|
+
else
|
|
336
|
+
raise "Unsupported script file type: #{extension}"
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def self.syntax(filename, text)
|
|
341
|
+
if text.nil?
|
|
342
|
+
return(
|
|
343
|
+
{ 'title' => 'Syntax Check Failed', 'description' => 'no text passed' }
|
|
344
|
+
)
|
|
345
|
+
end
|
|
346
|
+
language = detect_language(text, filename)
|
|
347
|
+
if language == 'ruby'
|
|
348
|
+
check_process = IO.popen('ruby -c -rubygems 2>&1', 'r+')
|
|
349
|
+
check_process.write("require 'openc3'; require 'openc3/script'; " + text)
|
|
350
|
+
check_process.close_write
|
|
351
|
+
results = check_process.readlines
|
|
352
|
+
check_process.close
|
|
353
|
+
if results
|
|
354
|
+
if results.any?(/Syntax OK/)
|
|
355
|
+
return(
|
|
356
|
+
{
|
|
357
|
+
'title' => 'Syntax Check Successful',
|
|
358
|
+
'description' => results.as_json().to_json(allow_nan: true),
|
|
359
|
+
}
|
|
360
|
+
)
|
|
361
|
+
else
|
|
362
|
+
# Results is an array of strings like this: ":2: syntax error ..."
|
|
363
|
+
# Normally the procedure comes before the first colon but since we
|
|
364
|
+
# are writing to the process this is blank so we throw it away
|
|
365
|
+
results.map! { |result| result.split(':')[1..-1].join(':') }
|
|
366
|
+
return(
|
|
367
|
+
{ 'title' => 'Syntax Check Failed', 'description' => results.as_json().to_json(allow_nan: true) }
|
|
368
|
+
)
|
|
369
|
+
end
|
|
370
|
+
else
|
|
371
|
+
return(
|
|
372
|
+
{
|
|
373
|
+
'title' => 'Syntax Check Exception',
|
|
374
|
+
'description' => 'Ruby syntax check unexpectedly returned nil',
|
|
375
|
+
}
|
|
376
|
+
)
|
|
377
|
+
end
|
|
378
|
+
elsif language == 'python'
|
|
379
|
+
# Python
|
|
380
|
+
tf = nil
|
|
381
|
+
begin
|
|
382
|
+
tf = Tempfile.new((['syntax', '.py']))
|
|
383
|
+
tf.write(text)
|
|
384
|
+
tf.close()
|
|
385
|
+
process_name = ENV['OPENC3_PYTHON_BIN'] || '/openc3/python/.venv/bin/python'
|
|
386
|
+
results, status = Open3.capture2e("#{process_name} -m py_compile #{tf.path}")
|
|
387
|
+
lines = []
|
|
388
|
+
success = status.respond_to?(:success?) ? status.success? : status == 0
|
|
389
|
+
if !success || (results and results.length > 0)
|
|
390
|
+
results.each_line do |line|
|
|
391
|
+
lines << line
|
|
392
|
+
end
|
|
393
|
+
lines << "Syntax Error" if lines.empty?
|
|
394
|
+
return(
|
|
395
|
+
{ 'title' => 'Syntax Check Failed', 'description' => lines.as_json().to_json(allow_nan: true) }
|
|
396
|
+
)
|
|
397
|
+
else
|
|
398
|
+
return(
|
|
399
|
+
{
|
|
400
|
+
'title' => 'Syntax Check Successful',
|
|
401
|
+
'description' => ["Syntax OK"].as_json().to_json(allow_nan: true),
|
|
402
|
+
}
|
|
403
|
+
)
|
|
404
|
+
end
|
|
405
|
+
ensure
|
|
406
|
+
tf.unlink if tf
|
|
407
|
+
end
|
|
408
|
+
else
|
|
409
|
+
# Script Engine Possibly
|
|
410
|
+
extension = File.extname(filename).to_s.downcase
|
|
411
|
+
script_engine_model = OpenC3::ScriptEngineModel.get_model(name: extension, scope: 'DEFAULT')
|
|
412
|
+
if script_engine_model
|
|
413
|
+
script_engine = script_engine_model.filename
|
|
414
|
+
if File.extname(script_engine).to_s.downcase == '.py'
|
|
415
|
+
process_name = ENV['OPENC3_PYTHON_BIN'] || '/openc3/python/.venv/bin/python'
|
|
416
|
+
runner_path = File.join(RAILS_ROOT, 'scripts', 'script_engine_cmd.py')
|
|
417
|
+
else
|
|
418
|
+
process_name = 'ruby'
|
|
419
|
+
runner_path = File.join(RAILS_ROOT, 'scripts', 'script_engine_cmd.rb')
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
tf = nil
|
|
423
|
+
begin
|
|
424
|
+
tf = Tempfile.new((['syntax', extension]))
|
|
425
|
+
tf.write(text)
|
|
426
|
+
tf.close()
|
|
427
|
+
results, status = Open3.capture2e("#{process_name} \"#{runner_path}\" syntax_check \"#{tf.path}\"")
|
|
428
|
+
lines = []
|
|
429
|
+
if results and results.length > 0
|
|
430
|
+
results.each_line do |line|
|
|
431
|
+
lines << line
|
|
432
|
+
end
|
|
433
|
+
return(
|
|
434
|
+
{ 'title' => 'Syntax Check Failed', 'description' => lines.as_json().to_json(allow_nan: true) }
|
|
435
|
+
)
|
|
436
|
+
else
|
|
437
|
+
return(
|
|
438
|
+
{
|
|
439
|
+
'title' => 'Syntax Check Successful',
|
|
440
|
+
'description' => ["Syntax OK"].as_json().to_json(allow_nan: true),
|
|
441
|
+
}
|
|
442
|
+
)
|
|
443
|
+
end
|
|
444
|
+
ensure
|
|
445
|
+
tf.unlink if tf
|
|
446
|
+
end
|
|
447
|
+
else
|
|
448
|
+
raise "Unsupported script file type: #{extension}"
|
|
449
|
+
end
|
|
450
|
+
end
|
|
451
|
+
end
|
|
452
|
+
end
|
|
@@ -47,7 +47,7 @@ module OpenC3
|
|
|
47
47
|
if secret.length < 3
|
|
48
48
|
raise ArgumentError, "Secret must have at least 3 items (type, key, data), got #{secret.length}"
|
|
49
49
|
end
|
|
50
|
-
type, key, data,
|
|
50
|
+
type, key, data, _secret_store = secret
|
|
51
51
|
case type
|
|
52
52
|
when 'ENV'
|
|
53
53
|
@local_secrets[key] = ENV[data]
|
data/lib/openc3/version.rb
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# encoding: ascii-8bit
|
|
2
2
|
|
|
3
|
-
OPENC3_VERSION = '7.0.0
|
|
3
|
+
OPENC3_VERSION = '7.0.0'
|
|
4
4
|
module OpenC3
|
|
5
5
|
module Version
|
|
6
6
|
MAJOR = '7'
|
|
7
7
|
MINOR = '0'
|
|
8
8
|
PATCH = '0'
|
|
9
|
-
OTHER = '
|
|
10
|
-
BUILD = '
|
|
9
|
+
OTHER = ''
|
|
10
|
+
BUILD = '463726bb8dab631febe2e502ec0462a460bd326c'
|
|
11
11
|
end
|
|
12
|
-
VERSION = '7.0.0
|
|
13
|
-
GEM_VERSION = '7.0.0
|
|
12
|
+
VERSION = '7.0.0'
|
|
13
|
+
GEM_VERSION = '7.0.0'
|
|
14
14
|
end
|
|
@@ -5,14 +5,6 @@ from openc3.conversions.conversion import Conversion
|
|
|
5
5
|
# Custom conversion class
|
|
6
6
|
# See https://docs.openc3.com/docs/configuration/conversions
|
|
7
7
|
class <%= conversion_class %>(Conversion):
|
|
8
|
-
def __init__(self):
|
|
9
|
-
super().__init__()
|
|
10
|
-
# Should be one of 'INT', 'UINT', 'FLOAT', 'STRING', 'BLOCK'
|
|
11
|
-
self.converted_type = 'STRING'
|
|
12
|
-
# Size of the converted type in bits
|
|
13
|
-
# Use 0 for 'STRING' or 'BLOCK' where the size can be variable
|
|
14
|
-
self.converted_bit_size = 0
|
|
15
|
-
|
|
16
8
|
# @param value [Object] Value based on the item definition. This could be
|
|
17
9
|
# a string, integer, float, or array of values.
|
|
18
10
|
# @param packet [Packet] The packet object where the conversion is defined
|
|
@@ -5,17 +5,6 @@ module OpenC3
|
|
|
5
5
|
# Custom conversion class
|
|
6
6
|
# See https://docs.openc3.com/docs/configuration/conversions
|
|
7
7
|
class <%= conversion_class %> < Conversion
|
|
8
|
-
def initialize
|
|
9
|
-
super()
|
|
10
|
-
# Should be one of :INT, :UINT, :FLOAT, :STRING, :BLOCK
|
|
11
|
-
@converted_type = :STRING
|
|
12
|
-
# Size of the converted type in bits
|
|
13
|
-
# Use 0 for :STRING or :BLOCK where the size can be variable
|
|
14
|
-
@converted_bit_size = 0
|
|
15
|
-
# return the arguments used
|
|
16
|
-
@params = nil
|
|
17
|
-
end
|
|
18
|
-
|
|
19
8
|
# @param value [Object] Value based on the item definition. This could be
|
|
20
9
|
# a string, integer, float, or array of values.
|
|
21
10
|
# @param packet [Packet] The packet object where the conversion is defined
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "<%= tool_name %>",
|
|
3
|
-
"version": "7.0.0
|
|
3
|
+
"version": "7.0.0",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"ng": "ng",
|
|
6
6
|
"start": "ng serve",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"@angular/platform-browser-dynamic": "^18.2.6",
|
|
24
24
|
"@angular/router": "^18.2.6",
|
|
25
25
|
"@astrouxds/astro-web-components": "^7.24.0",
|
|
26
|
-
"@openc3/js-common": "
|
|
26
|
+
"@openc3/js-common": "7.0.0",
|
|
27
27
|
"rxjs": "~7.8.0",
|
|
28
28
|
"single-spa": "^5.9.5",
|
|
29
29
|
"single-spa-angular": "^9.2.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "<%= tool_name %>",
|
|
3
|
-
"version": "7.0.0
|
|
3
|
+
"version": "7.0.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@astrouxds/astro-web-components": "^7.24.0",
|
|
14
|
-
"@openc3/js-common": "
|
|
15
|
-
"@openc3/vue-common": "
|
|
14
|
+
"@openc3/js-common": "7.0.0",
|
|
15
|
+
"@openc3/vue-common": "7.0.0",
|
|
16
16
|
"axios": "^1.7.7",
|
|
17
17
|
"date-fns": "^4.1.0",
|
|
18
18
|
"lodash": "^4.17.21",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "<%= widget_name %>",
|
|
3
|
-
"version": "7.0.0
|
|
3
|
+
"version": "7.0.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"@astrouxds/astro-web-components": "^7.24.0",
|
|
11
|
-
"@openc3/vue-common": "
|
|
11
|
+
"@openc3/vue-common": "7.0.0",
|
|
12
12
|
"vuetify": "^3.7.1"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|