Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions openc3-cosmos-cmd-tlm-api/app/controllers/scopes_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,20 @@ def create(update_model = false)
return unless authorization('superadmin')
model = @model_class.from_json(params[:json])
if update_model
existing = @model_class.get(name: model.name)
model.update
# Scopes are global so the scope is always 'DEFAULT'
OpenC3::Logger.info("#{@model_class.name} updated: #{params[:json]}", scope: 'DEFAULT', user: username())
changes = existing ? model.diff(existing) : nil
if changes.nil? || changes.any?
OpenC3::Logger.info("User #{username()} updated scope '#{model.name}': #{(changes || [params[:json]]).join(', ')}", scope: 'DEFAULT', user: username())
else
OpenC3::Logger.info("User #{username()} updated scope '#{model.name}' (no changes)", scope: 'DEFAULT', user: username())
end
else
model.create
model.deploy(".", {})
# Scopes are global so the scope is always 'DEFAULT'
OpenC3::Logger.info("#{@model_class.name} created: #{params[:json]}", scope: 'DEFAULT', user: username())
OpenC3::Logger.info("User #{username()} created scope '#{model.name}'", scope: 'DEFAULT', user: username())
end
head :ok
rescue Exception => e
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!--
# Copyright 2025 OpenC3, Inc.
# Copyright 2026 OpenC3, Inc.
# All Rights Reserved.
#
# This program is distributed in the hope that it will be useful,
Expand All @@ -15,8 +15,12 @@
<v-card>
<v-card-title> Code Editor Settings </v-card-title>
<v-card-subtitle>
Settings for the code editors built into COSMOS (e.g. in Script Runner)
Settings for the code editors built into COSMOS (e.g. in Script Runner).
These settings are saved to your browser's local storage.
</v-card-subtitle>
<v-alert v-model="successSaving" type="success" closable density="compact">
Saved to local browser settings! (Refresh the page to see changes)
</v-alert>
<v-card-text class="pb-0">
<v-select
v-model="defaultLanguage"
Expand Down Expand Up @@ -49,6 +53,7 @@ import { AceEditorUtils } from '@/components/ace'
export default {
data() {
return {
successSaving: false,
vimMode: AceEditorUtils.isVimModeEnabled(),
defaultLanguage: AceEditorUtils.getDefaultScriptingLanguage(),
languageOptions: [
Expand All @@ -61,6 +66,7 @@ export default {
save: function () {
AceEditorUtils.setVimMode(this.vimMode)
AceEditorUtils.setDefaultScriptingLanguage(this.defaultLanguage)
this.successSaving = true
},
},
}
Expand Down
31 changes: 14 additions & 17 deletions openc3-cosmos-script-runner-api/scripts/run_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,23 +51,20 @@ def run_script_log(script_id, message, color="BLACK", message_log=True):
EphemeralStore.instance()

# Clear ENV vars for security purposes
del os.environ["OPENC3_BUCKET_USERNAME"]
del os.environ["OPENC3_BUCKET_PASSWORD"]
os.unsetenv("OPENC3_BUCKET_USERNAME")
os.unsetenv("OPENC3_BUCKET_PASSWORD")
del os.environ["OPENC3_REDIS_USERNAME"]
del os.environ["OPENC3_REDIS_PASSWORD"]
os.unsetenv("OPENC3_TSDB_USERNAME")
os.unsetenv("OPENC3_TSDB_PASSWORD")
del os.environ["OPENC3_TSDB_USERNAME"]
del os.environ["OPENC3_TSDB_PASSWORD"]
os.unsetenv("OPENC3_API_PASSWORD")
del os.environ["OPENC3_API_PASSWORD"]
os.unsetenv("OPENC3_SERVICE_PASSWORD")
del os.environ["OPENC3_SERVICE_PASSWORD"]
# This actually contains the password via redis://openc3:XXXXXXXX@openc3-redis:6379
os.unsetenv("ANYCABLE_REDIS_URL")
del os.environ["ANYCABLE_REDIS_URL"]
# Use pop with default to avoid KeyError if vars don't exist (e.g. Enterprise/Keycloak)
for key in [
"OPENC3_BUCKET_USERNAME",
"OPENC3_BUCKET_PASSWORD",
"OPENC3_REDIS_USERNAME",
"OPENC3_REDIS_PASSWORD",
"OPENC3_TSDB_USERNAME",
"OPENC3_TSDB_PASSWORD",
"OPENC3_API_PASSWORD",
"OPENC3_SERVICE_PASSWORD",
# This actually contains the password via redis://openc3:XXXXXXXX@openc3-redis:6379
"ANYCABLE_REDIS_URL",
]:
os.environ.pop(key, None)


script_id = sys.argv[1]
Expand Down
2 changes: 2 additions & 0 deletions openc3/lib/openc3/api/settings_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ def set_setting(name, data, manual: false, scope: $openc3_scope, token: $openc3_
authorize(permission: 'admin', manual: manual, scope: scope, token: token)
SettingModel.set({ name: name, data: data }, scope: scope)
LocalMode.save_setting(scope, name, data)
username = user_info(token)['username'] || 'Anonymous'
Logger.info("User #{username} saved setting '#{name}': #{data}", scope: scope, user: username)
end
# save_setting is DEPRECATED
alias save_setting set_setting
Expand Down
16 changes: 16 additions & 0 deletions openc3/lib/openc3/models/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,22 @@ def as_json(*a)
'scope' => @scope }
end

# Compare this model's as_json with a previous Hash and return an array
# of human-readable change descriptions (e.g. "key: old -> new").
# Skips the updated_at field since it always changes.
# @param existing [Hash] the previous model state (from .get)
# @return [Array<String>] list of changed fields
def diff(existing)
changes = []
new_json = as_json
existing.each do |key, old_value|
next if key == 'updated_at'
new_value = new_json[key]
changes << "#{key}: #{old_value} -> #{new_value}" if old_value != new_value
end
changes
end

def check_disable_erb(filename)
erb_disabled = false
if @disable_erb
Expand Down
7 changes: 5 additions & 2 deletions openc3/python/openc3/api/settings_api.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2023 OpenC3, Inc
# Copyright 2026 OpenC3, Inc
# All Rights Reserved.
#
# This program is distributed in the hope that it will be useful,
Expand All @@ -12,8 +12,9 @@
from openc3.api import WHITELIST
from openc3.environment import OPENC3_SCOPE
from openc3.models.setting_model import SettingModel
from openc3.utilities.authorization import authorize
from openc3.utilities.authorization import authorize, user_info
from openc3.utilities.local_mode import LocalMode
from openc3.utilities.logger import Logger


WHITELIST.extend(
Expand Down Expand Up @@ -60,6 +61,8 @@ def set_setting(name, data, local_mode=True, scope=OPENC3_SCOPE):
SettingModel.set({"name": name, "data": data}, scope=scope)
if local_mode:
LocalMode.save_setting(scope, name, data)
username = user_info(None).get("username") or "Anonymous"
Logger.info(f"User {username} saved setting '{name}': {data}", scope=scope, user=username)


# DEPRECATED
Expand Down
14 changes: 14 additions & 0 deletions openc3/python/openc3/models/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,20 @@ def destroy(self):
self.undeploy()
self.store().hdel(self.primary_key, self.name)

def diff(self, existing):
"""Compare this model's as_json with a previous dict and return a list
of human-readable change descriptions (e.g. "key: old -> new").
Skips the updated_at field since it always changes."""
changes = []
new_json = self.as_json()
for key, old_value in existing.items():
if key == "updated_at":
continue
new_value = new_json.get(key)
if old_value != new_value:
changes.append(f"{key}: {old_value} -> {new_value}")
return changes

def as_json(self):
"""
Return:
Expand Down
Loading