fprime-gds 3.3.2__py3-none-any.whl → 3.4.0__py3-none-any.whl
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.
- fprime_gds/common/communication/framing.py +18 -13
- fprime_gds/common/communication/ground.py +1 -1
- fprime_gds/common/communication/updown.py +26 -5
- fprime_gds/common/data_types/event_data.py +9 -3
- fprime_gds/common/files/downlinker.py +19 -6
- fprime_gds/common/files/helpers.py +1 -0
- fprime_gds/common/logger/__init__.py +11 -2
- fprime_gds/common/pipeline/files.py +8 -1
- fprime_gds/executables/cli.py +18 -8
- fprime_gds/executables/comm.py +53 -26
- fprime_gds/executables/run_deployment.py +5 -5
- fprime_gds/flask/app.py +21 -7
- fprime_gds/flask/default_settings.py +4 -2
- fprime_gds/flask/json.py +48 -43
- fprime_gds/flask/static/addons/advanced-settings/addon-templates.js +4 -0
- fprime_gds/flask/static/addons/advanced-settings/addon.js +13 -7
- fprime_gds/flask/static/addons/channel-render/channel-render.js +8 -2
- fprime_gds/flask/static/addons/commanding/argument-templates.js +14 -3
- fprime_gds/flask/static/addons/commanding/arguments.js +53 -6
- fprime_gds/flask/static/js/datastore.js +25 -4
- fprime_gds/flask/static/js/settings.js +2 -1
- fprime_gds/flask/static/js/validate.js +5 -0
- fprime_gds/flask/static/js/vue-support/event.js +1 -1
- fprime_gds/flask/static/js/vue-support/log.js +21 -13
- {fprime_gds-3.3.2.dist-info → fprime_gds-3.4.0.dist-info}/METADATA +2 -2
- {fprime_gds-3.3.2.dist-info → fprime_gds-3.4.0.dist-info}/RECORD +31 -31
- {fprime_gds-3.3.2.dist-info → fprime_gds-3.4.0.dist-info}/WHEEL +1 -1
- {fprime_gds-3.3.2.dist-info → fprime_gds-3.4.0.dist-info}/LICENSE.txt +0 -0
- {fprime_gds-3.3.2.dist-info → fprime_gds-3.4.0.dist-info}/NOTICE.txt +0 -0
- {fprime_gds-3.3.2.dist-info → fprime_gds-3.4.0.dist-info}/entry_points.txt +0 -0
- {fprime_gds-3.3.2.dist-info → fprime_gds-3.4.0.dist-info}/top_level.txt +0 -0
fprime_gds/flask/json.py
CHANGED
@@ -20,7 +20,7 @@ from fprime_gds.common.templates.data_template import DataTemplate
|
|
20
20
|
|
21
21
|
|
22
22
|
def jsonify_base_type(input_type: Type[BaseType]) -> dict:
|
23
|
-
"""
|
23
|
+
"""Turn a base type into a JSONable dictionary
|
24
24
|
|
25
25
|
Convert a BaseType (the type, not an instance) into a jsonable dictionary. BaseTypes are converted by reading the
|
26
26
|
class properties (without __) and creating the object:
|
@@ -36,14 +36,17 @@ def jsonify_base_type(input_type: Type[BaseType]) -> dict:
|
|
36
36
|
json-able dictionary representing the type
|
37
37
|
"""
|
38
38
|
assert issubclass(input_type, BaseType), "Failure to properly encode data"
|
39
|
-
members = getmembers(
|
39
|
+
members = getmembers(
|
40
|
+
input_type,
|
41
|
+
lambda value: not isroutine(value) and not isinstance(value, property),
|
42
|
+
)
|
40
43
|
jsonable_dict = {name: value for name, value in members if not name.startswith("_")}
|
41
44
|
jsonable_dict.update({"name": input_type.__name__})
|
42
45
|
return jsonable_dict
|
43
46
|
|
44
47
|
|
45
48
|
def getter_based_json(obj):
|
46
|
-
"""
|
49
|
+
"""Converts objects to JSON via get_ methods
|
47
50
|
|
48
51
|
Template functions define a series of get_* methods whose return values need to be serialized. This function
|
49
52
|
handles that data.
|
@@ -80,7 +83,7 @@ def getter_based_json(obj):
|
|
80
83
|
|
81
84
|
|
82
85
|
def minimal_event(obj):
|
83
|
-
"""
|
86
|
+
"""Minimal event encoding: time, id, display_text
|
84
87
|
|
85
88
|
Events need time, id, display_text. No other information from the event is necessary for the display. This will
|
86
89
|
minimally encode the data for JSON.
|
@@ -95,7 +98,7 @@ def minimal_event(obj):
|
|
95
98
|
|
96
99
|
|
97
100
|
def minimal_channel(obj):
|
98
|
-
"""
|
101
|
+
"""Minimal channel serialization: time, id, val, and display_text
|
99
102
|
|
100
103
|
Minimally serializes channel values for use with the flask layer. This does away with any unnecessary data by
|
101
104
|
serializing only the id, value, and optional display text
|
@@ -106,11 +109,16 @@ def minimal_channel(obj):
|
|
106
109
|
Returns:
|
107
110
|
JSON compatible python anonymous type (dictionary)
|
108
111
|
"""
|
109
|
-
return {
|
112
|
+
return {
|
113
|
+
"time": obj.time,
|
114
|
+
"id": obj.id,
|
115
|
+
"val": obj.val_obj.val,
|
116
|
+
"display_text": obj.display_text,
|
117
|
+
}
|
110
118
|
|
111
119
|
|
112
120
|
def minimal_command(obj):
|
113
|
-
"""
|
121
|
+
"""Minimal command serialization: time, id, and args values
|
114
122
|
|
115
123
|
Minimally serializes the command values for use with the flask layer. This prevents excess data by keeping the data
|
116
124
|
to the minimum instance data for commands including: time, opcode (id), and the value for args.
|
@@ -125,7 +133,7 @@ def minimal_command(obj):
|
|
125
133
|
|
126
134
|
|
127
135
|
def time_type(obj):
|
128
|
-
"""
|
136
|
+
"""Time type serialization
|
129
137
|
|
130
138
|
Serializes the time type into a JSON compatible object.
|
131
139
|
|
@@ -137,49 +145,46 @@ def time_type(obj):
|
|
137
145
|
"""
|
138
146
|
assert isinstance(obj, TimeType), "Incorrect type for serialization method"
|
139
147
|
return {
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
148
|
+
"base": obj.timeBase.value,
|
149
|
+
"context": obj.timeContext,
|
150
|
+
"seconds": obj.seconds,
|
151
|
+
"microseconds": obj.useconds,
|
152
|
+
}
|
145
153
|
|
146
154
|
|
147
155
|
def enum_json(obj):
|
148
|
-
"""
|
156
|
+
"""Jsonify the python enums!"""
|
149
157
|
enum_dict = {"value": str(obj), "values": {}}
|
150
158
|
for enum_val in type(obj):
|
151
159
|
enum_dict["values"][str(enum_val)] = enum_val.value
|
152
160
|
return enum_dict
|
153
161
|
|
154
162
|
|
155
|
-
|
156
|
-
|
157
|
-
|
163
|
+
JSON_ENCODERS = {
|
164
|
+
ABCMeta: jsonify_base_type,
|
165
|
+
UUID: str,
|
166
|
+
ChData: minimal_channel,
|
167
|
+
EventData: minimal_event,
|
168
|
+
CmdData: minimal_command,
|
169
|
+
TimeType: time_type,
|
170
|
+
}
|
171
|
+
|
172
|
+
|
173
|
+
def default(obj):
|
158
174
|
"""
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
ChData: minimal_channel,
|
163
|
-
EventData: minimal_event,
|
164
|
-
CmdData: minimal_command,
|
165
|
-
TimeType: time_type
|
166
|
-
}
|
175
|
+
Override the default JSON encoder to pull out a dictionary for our handled types for encoding with the default
|
176
|
+
encoder built into flask. This function must convert the given object into a JSON compatable python object (e.g.
|
177
|
+
using lists, dictionaries, strings, and primitive types).
|
167
178
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
return getter_based_json(obj)
|
181
|
-
if isinstance(obj, Enum):
|
182
|
-
return enum_json(obj)
|
183
|
-
if isinstance(obj, ValueType):
|
184
|
-
return obj.val
|
185
|
-
return flask.json.JSONEncoder.default(self, obj)
|
179
|
+
:param obj: obj to encode
|
180
|
+
:return: JSON
|
181
|
+
"""
|
182
|
+
if type(obj) in JSON_ENCODERS:
|
183
|
+
return JSON_ENCODERS[type(obj)](obj)
|
184
|
+
if isinstance(obj, DataTemplate):
|
185
|
+
return getter_based_json(obj)
|
186
|
+
if isinstance(obj, Enum):
|
187
|
+
return enum_json(obj)
|
188
|
+
if isinstance(obj, ValueType):
|
189
|
+
return obj.val
|
190
|
+
return flask.json.provider.DefaultJSONProvider.default(obj)
|
@@ -9,6 +9,10 @@ export let advanced_template = `
|
|
9
9
|
<h3>{{ setting_category.replace("_", " ") }}</h3>
|
10
10
|
<div v-html="settings[setting_category].description"></div>
|
11
11
|
<div class="input-group mb-3" v-for="setting_key in Object.keys(settings[setting_category].settings)">
|
12
|
+
<small v-if="(settings[setting_category].descriptions || {})[setting_key]">
|
13
|
+
<strong>{{ setting_key }}</strong>
|
14
|
+
{{ settings[setting_category].descriptions[setting_key]}}
|
15
|
+
</small>
|
12
16
|
<div class="input-group-prepend col-6">
|
13
17
|
<span class="input-group-text col-12">{{ setting_key }}</span>
|
14
18
|
</div>
|
@@ -19,13 +19,19 @@ Vue.component("advanced-settings", {
|
|
19
19
|
settings: _settings.polling_intervals
|
20
20
|
},
|
21
21
|
"Miscellaneous": {
|
22
|
-
description: "Miscellaneous settings for GDS UI operations."
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
"
|
22
|
+
description: "Miscellaneous settings for GDS UI operations.",
|
23
|
+
descriptions: {
|
24
|
+
event_buffer_size: "Maximum number of events stored by the GDS. When exceeded, oldest events are dropped " +
|
25
|
+
"Lower this value if performance drops on the Events tab. Default: -1, no limit.",
|
26
|
+
command_buffer_size: "Maximum number of commands stored by the GDS. When exceeded, oldest commands are dropped " +
|
27
|
+
"Lower this value if performance drops on the Commanding tab. Default: -1, no limit.",
|
28
|
+
response_object_limit: "Limit to the number of objects returned by one POLL request to the backend. " +
|
29
|
+
"Lower this value if polling times are longer than polling intervals. Default: 6000.",
|
30
|
+
compact_commanding: "Use the compact form for command arguments. In this form, Array and Serializable type " +
|
31
|
+
"inputs are flattened into a sequential set of input boxes without extraneous structure.",
|
32
|
+
channels_display_last_received: "When set, any channel received will update the displayed value. Otherwise " +
|
33
|
+
"only channels with newer timestamps update the displayed value."
|
34
|
+
},
|
29
35
|
settings: _settings.miscellaneous
|
30
36
|
}
|
31
37
|
},
|
@@ -107,7 +107,13 @@ Vue.component("channel-render", {
|
|
107
107
|
* @returns: display text of item/child item
|
108
108
|
*/
|
109
109
|
displayText() {
|
110
|
-
|
110
|
+
let possibles = [this.item?.display_text, this.item?.val, this.val];
|
111
|
+
for (let i = 0; i < possibles.length; i++) {
|
112
|
+
if (typeof(possibles[i]) !== "undefined" && possibles[i] !== null) {
|
113
|
+
return possibles[i];
|
114
|
+
}
|
115
|
+
}
|
116
|
+
return "";
|
111
117
|
}
|
112
118
|
|
113
119
|
}
|
@@ -120,4 +126,4 @@ let channel_row_data = {
|
|
120
126
|
/**
|
121
127
|
* channel-row replaces fp-row in display template only.
|
122
128
|
*/
|
123
|
-
Vue.component("channel-row", channel_row_data);
|
129
|
+
Vue.component("channel-row", channel_row_data);
|
@@ -15,6 +15,17 @@ export let command_enum_argument_template = `
|
|
15
15
|
</v-select>
|
16
16
|
`;
|
17
17
|
|
18
|
+
/**
|
19
|
+
* Enum argument uses the v-select dropdown to render the various choices while providing search and match capabilities.
|
20
|
+
*/
|
21
|
+
export let command_bool_argument_template = `
|
22
|
+
<v-select :id="argument.name" style="flex: 1 1 auto; background-color: white;"
|
23
|
+
:clearable="false" :searchable="true" @input="validateTrigger"
|
24
|
+
:filterable="true" label="full_name" :options="['True', 'False']"
|
25
|
+
v-model="argument.value" class="fprime-input" :class="argument.error == '' ? '' : 'is-invalid'" required>
|
26
|
+
</v-select>
|
27
|
+
`;
|
28
|
+
|
18
29
|
/**
|
19
30
|
* Serializable arguments "flatten" the structure into a list of fields.
|
20
31
|
*/
|
@@ -59,13 +70,13 @@ export let command_array_argument_template = `
|
|
59
70
|
* enumerations are handled here as they represent a single scalar input.
|
60
71
|
*/
|
61
72
|
export let command_scalar_argument_template = `
|
62
|
-
<div style="display: contents;">
|
73
|
+
<div style="display: contents;" class="fprime-scalar-argument">
|
63
74
|
<div class="form-group col-md-6">
|
64
75
|
<label :for="argument.name" class="control-label font-weight-bold">
|
65
76
|
{{ argument.name + ((argument.description != null) ? ": " + argument.description : "") }}
|
66
77
|
</label>
|
67
|
-
|
68
|
-
<command-enum-argument v-if="argument.type.ENUM_DICT" :argument="argument"></command-enum-argument>
|
78
|
+
<command-bool-argument v-if="argument.type.name == 'BoolType'" :argument="argument"></command-bool-argument>
|
79
|
+
<command-enum-argument v-else-if="argument.type.ENUM_DICT" :argument="argument"></command-enum-argument>
|
69
80
|
<input v-else :type="inputType[0]" v-bind:id="argument.name" class="form-control fprime-input"
|
70
81
|
:placeholder="argument.name" :pattern="inputType[1]" :step="inputType[2]" v-on:input="validateTrigger"
|
71
82
|
v-model="argument.value" :class="argument.error == '' ? '' : 'is-invalid'" required>
|
@@ -8,6 +8,7 @@
|
|
8
8
|
*/
|
9
9
|
import "../../third-party/js/vue-select.js"
|
10
10
|
import {
|
11
|
+
command_bool_argument_template,
|
11
12
|
command_enum_argument_template,
|
12
13
|
command_array_argument_template,
|
13
14
|
command_serializable_argument_template,
|
@@ -118,6 +119,35 @@ export function squashify_argument(argument) {
|
|
118
119
|
let field = argument.type.MEMBER_LIST[i][0];
|
119
120
|
value[field] = squashify_argument(argument.value[field]);
|
120
121
|
}
|
122
|
+
} else if (["U64Type", "U32Type", "U16Type", "U8Type"].indexOf(argument.type.name) != -1) {
|
123
|
+
if (argument.value.startsWith("0x")) {
|
124
|
+
// Hexadecimal
|
125
|
+
value = parseInt(argument.value, 16);
|
126
|
+
} else if (argument.value.startsWith("0b")) {
|
127
|
+
// Binary
|
128
|
+
value = parseInt(argument.value.slice(2), 2);
|
129
|
+
} else if (argument.value.startsWith("0o")) {
|
130
|
+
// Octal
|
131
|
+
value = parseInt(argument.value.slice(2), 8);
|
132
|
+
} else {
|
133
|
+
// Decimal
|
134
|
+
value = parseInt(argument.value, 10);
|
135
|
+
}
|
136
|
+
}
|
137
|
+
else if (["I64Type", "I32Type", "I16Type", "I8Type"].indexOf(argument.type.name) != -1) {
|
138
|
+
value = parseInt(argument.value, 10);
|
139
|
+
}
|
140
|
+
else if (["F64Type", "F32Type"].indexOf(argument.type.name) != -1) {
|
141
|
+
value = parseFloat(argument.value);
|
142
|
+
}
|
143
|
+
else if (argument.type.name == "BoolType") {
|
144
|
+
if ((typeof(value) === "string") && (["true", "yes"].indexOf(value.toLowerCase()) !== -1)) {
|
145
|
+
value = true;
|
146
|
+
} else if ((typeof(value) === "string") && (["false", "no"].indexOf(value.toLowerCase()) !== -1)) {
|
147
|
+
value = false;
|
148
|
+
} else {
|
149
|
+
console.assert(typeof(value) !== "boolean", "Cannot process boolean, invalid input type")
|
150
|
+
}
|
121
151
|
}
|
122
152
|
return value;
|
123
153
|
}
|
@@ -174,13 +204,22 @@ let base_argument_component_properties = {
|
|
174
204
|
validateArgument(recurse_down) {
|
175
205
|
recurse_down = !!(recurse_down); // Force recurse_down to be defined as a boolean
|
176
206
|
let valid = validate_input(this.argument);
|
177
|
-
// HTML
|
207
|
+
// Each scalar argument needs to set custom validity on the HTML input that it owns. However, non-scalar
|
208
|
+
// inputs skip this step less the first scalar child's input box be poisoned with incorrect validity due
|
209
|
+
// to the unbounded recursive nature of getElementsByClassName used to find a fprime-input children.
|
210
|
+
let is_scalar = [...document.getElementsByClassName("fprime-scalar-argument")]
|
211
|
+
.filter((scalar) => scalar === this.$el || scalar.contains(this.$el)).length > 0;
|
212
|
+
|
213
|
+
// Now grab the singular the nearest input and report validity if and only if this is a scalar. Non-
|
214
|
+
// scalar values are compositions of scalar children and thus validity will be set when the scalar
|
215
|
+
// itself is validated.
|
178
216
|
let input_element = this.$el.getElementsByClassName("fprime-input")[0] || this.$el;
|
179
|
-
if (input_element.setCustomValidity && input_element.reportValidity) {
|
217
|
+
if (is_scalar && input_element.setCustomValidity && input_element.reportValidity) {
|
180
218
|
input_element.setCustomValidity(this.argument.error);
|
181
219
|
input_element.reportValidity();
|
182
220
|
}
|
183
|
-
//
|
221
|
+
// Validation can happen recursively down through the children, or up through the parent in order to
|
222
|
+
// laterally validate complex arguments when a single input scalar filed is adjusted.
|
184
223
|
let recursive_listing = (recurse_down) ? this.$children.slice().reverse() : [this.$parent];
|
185
224
|
let valid_recursion = (recursive_listing || []).reduce(
|
186
225
|
(accumulator, next_element) => {
|
@@ -225,6 +264,14 @@ Vue.component("command-enum-argument", {
|
|
225
264
|
template: command_enum_argument_template,
|
226
265
|
});
|
227
266
|
|
267
|
+
/**
|
268
|
+
* Special boolean processing component to render as a drop-down.
|
269
|
+
*/
|
270
|
+
Vue.component("command-bool-argument", {
|
271
|
+
...base_argument_component_properties,
|
272
|
+
template: command_bool_argument_template,
|
273
|
+
});
|
274
|
+
|
228
275
|
/**
|
229
276
|
* Scalar argument processing. Sets up the input type such that numbers can be input with the correct formatting.
|
230
277
|
*/
|
@@ -239,14 +286,14 @@ Vue.component("command-scalar-argument", {
|
|
239
286
|
*/
|
240
287
|
inputType() {
|
241
288
|
// Unsigned integer
|
242
|
-
if (this.argument.type.name
|
289
|
+
if (["U64Type", "U32Type", "U16Type", "U8Type"].indexOf(this.argument.type.name) != -1) {
|
243
290
|
// Supports binary, hex, octal, and digital
|
244
291
|
return ["text", "0[bB][01]+|0[oO][0-7]+|0[xX][0-9a-fA-F]+|[1-9]\\d*|0", ""];
|
245
292
|
}
|
246
|
-
else if (this.argument.type.name
|
293
|
+
else if (["I64Type", "I32Type", "I16Type", "I8Type"].indexOf(this.argument.type.name) != -1) {
|
247
294
|
return ["number", null, "1"];
|
248
295
|
}
|
249
|
-
else if (this.argument.type.name
|
296
|
+
else if (["F64Type", "F32Type"].indexOf(this.argument.type.name) != -1) {
|
250
297
|
return ["number", null, "any"];
|
251
298
|
}
|
252
299
|
return ["text", ".*", null];
|
@@ -28,6 +28,7 @@ class HistoryHelper {
|
|
28
28
|
this.active_key = active_key;
|
29
29
|
this.consumers = [];
|
30
30
|
this.active_timeout = null;
|
31
|
+
this.counter = 0;
|
31
32
|
}
|
32
33
|
|
33
34
|
/**
|
@@ -58,7 +59,8 @@ class HistoryHelper {
|
|
58
59
|
// Break our when no new items returned
|
59
60
|
if (new_items.length === 0) { return; }
|
60
61
|
new_items.filter((item) => item.time).forEach((item) => {
|
61
|
-
item.datetime = timeToDate(item.time)
|
62
|
+
item.datetime = timeToDate(item.time);
|
63
|
+
item.incremental_id = this.counter++;
|
62
64
|
});
|
63
65
|
this.consumers.forEach((consumer) => {
|
64
66
|
try {
|
@@ -119,7 +121,18 @@ class FullListHistory extends ListHistory {
|
|
119
121
|
* @param new_items: new items being to be process
|
120
122
|
*/
|
121
123
|
send(new_items) {
|
122
|
-
|
124
|
+
// When the lists are not the same, update the stored list otherwise keep the list to prevent unnecessary bound
|
125
|
+
// data re-rendering.
|
126
|
+
if (this.store.length !== new_items.length) {
|
127
|
+
this.store.splice(0, this.store.length, ...new_items);
|
128
|
+
return;
|
129
|
+
}
|
130
|
+
for (let i = 0; i < Math.min(this.store.length, new_items.length); i++) {
|
131
|
+
if (this.store[i] !== new_items[i]) {
|
132
|
+
this.store.splice(0, this.store.length, ...new_items);
|
133
|
+
return;
|
134
|
+
}
|
135
|
+
}
|
123
136
|
}
|
124
137
|
}
|
125
138
|
|
@@ -148,8 +161,12 @@ class MappedHistory extends HistoryHelper {
|
|
148
161
|
let updated = {};
|
149
162
|
for (let i = 0; i < new_items.length; i++) {
|
150
163
|
let item = new_items[i];
|
151
|
-
//
|
152
|
-
if (
|
164
|
+
// When displaying last received, update the value always
|
165
|
+
if (_settings.miscellaneous.channels_display_last_received) {
|
166
|
+
updated[item.id] = item;
|
167
|
+
}
|
168
|
+
// Otherwise check for a newer timestamp
|
169
|
+
else if ((this.store[item.id] || null) === null || item.datetime >= this.store[item.id].datetime) {
|
153
170
|
updated[item.id] = item;
|
154
171
|
}
|
155
172
|
}
|
@@ -270,6 +287,10 @@ class DataStore {
|
|
270
287
|
if (argument.type.ENUM_DICT) {
|
271
288
|
argument.value = Object.keys(argument.type.ENUM_DICT)[0];
|
272
289
|
}
|
290
|
+
// Booleans are initialized to True
|
291
|
+
else if (argument.type.name === "BoolType") {
|
292
|
+
argument.value = "True";
|
293
|
+
}
|
273
294
|
// Arrays expand to a set length of N pseudo-arguments
|
274
295
|
else if (argument.type.LENGTH) {
|
275
296
|
let array_length = argument.type.LENGTH;
|
@@ -116,6 +116,11 @@ export function validate_scalar_input(argument) {
|
|
116
116
|
argument.error = "";
|
117
117
|
return true;
|
118
118
|
}
|
119
|
+
// Boolean type handling
|
120
|
+
else if (argument.type.name === "BoolType") {
|
121
|
+
return (argument.value == null) ? null :
|
122
|
+
["yes", "no", "true", "false"].indexOf(argument.value.toString().toLowerCase()) >= 0;
|
123
|
+
}
|
119
124
|
console.assert(false, "Unknown scalar type: " + argument.type.name);
|
120
125
|
argument.error = "";
|
121
126
|
return true;
|
@@ -109,7 +109,7 @@ Vue.component("event-list", {
|
|
109
109
|
* @return {string} unique key
|
110
110
|
*/
|
111
111
|
keyify(item) {
|
112
|
-
return "evt-" + item.id + "-" + item.time.seconds + "-"+ item.time.microseconds;
|
112
|
+
return "evt-" + item.id + "-" + item.time.seconds + "-"+ item.time.microseconds + "-" + item.incremental_id;
|
113
113
|
},
|
114
114
|
/**
|
115
115
|
* A function to clear events out of the data store. This is to reset the events entirely.
|
@@ -21,9 +21,11 @@ let template = `
|
|
21
21
|
<div class="my-3">
|
22
22
|
<v-select id="logselect"
|
23
23
|
:clearable="true" :searchable="true"
|
24
|
-
:filterable="true" :options="
|
24
|
+
:filterable="true" :options="logs"
|
25
25
|
v-model="selected">
|
26
26
|
</v-select>
|
27
|
+
<input name="scroll" type="checkbox" v-model="scroll" />
|
28
|
+
<label for="scroll">Scroll Log Output</label>
|
27
29
|
</div>
|
28
30
|
</div>
|
29
31
|
<div class="fp-scroll-container">
|
@@ -31,6 +33,7 @@ let template = `
|
|
31
33
|
<pre><code>{{ text }}</code></pre>
|
32
34
|
</div>
|
33
35
|
</div>
|
36
|
+
<div class="alert alert-danger" role="alert" v-if="error">{{ error }}</div>
|
34
37
|
</div>
|
35
38
|
`;
|
36
39
|
|
@@ -40,19 +43,10 @@ Vue.component('v-select', VueSelect.VueSelect);
|
|
40
43
|
|
41
44
|
Vue.component("logging", {
|
42
45
|
template: template,
|
43
|
-
data() {return {"selected": "", "logs": _datastore.logs, text: ""}},
|
46
|
+
data() {return {"selected": "", "logs": _datastore.logs, text: "", "scroll": true, "error": ""}},
|
44
47
|
mounted() {
|
45
48
|
setInterval(this.update, 1000); // Grab log updates once a second
|
46
49
|
},
|
47
|
-
computed:{
|
48
|
-
/**
|
49
|
-
* Computes the appropriate log files available.
|
50
|
-
* @return {string[]}
|
51
|
-
*/
|
52
|
-
options: function () {
|
53
|
-
return this.logs;
|
54
|
-
}
|
55
|
-
},
|
56
50
|
methods: {
|
57
51
|
/**
|
58
52
|
* Updates the log data such that new logs can be displayed.
|
@@ -65,8 +59,22 @@ Vue.component("logging", {
|
|
65
59
|
_loader.load("/logdata/" + this.selected, "GET").then(
|
66
60
|
(result) => {
|
67
61
|
_self.text = result[_self.selected];
|
68
|
-
|
69
|
-
|
62
|
+
// Update on next-tick so that the updated content has been drawn already
|
63
|
+
_self.$nextTick(() => {
|
64
|
+
let panes = _self.$el.getElementsByClassName("fp-scrollable");
|
65
|
+
if (panes && _self.scroll)
|
66
|
+
{
|
67
|
+
panes[0].scrollTop = panes[0].scrollHeight;
|
68
|
+
}
|
69
|
+
});
|
70
|
+
_self.error = "";
|
71
|
+
}).catch((result) => {
|
72
|
+
if (result === "") {
|
73
|
+
_self.error = "[ERROR] Failed to update log content.";
|
74
|
+
} else {
|
75
|
+
_self.error = "[ERROR] " + result + ".";
|
76
|
+
}
|
77
|
+
});
|
70
78
|
}
|
71
79
|
}
|
72
80
|
});
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: fprime-gds
|
3
|
-
Version: 3.
|
3
|
+
Version: 3.4.0
|
4
4
|
Summary: F Prime Flight Software Ground Data System layer.
|
5
5
|
Home-page: https://github.com/nasa/fprime
|
6
6
|
Author: Michael Starch
|
@@ -23,7 +23,7 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
23
23
|
Requires-Python: >=3.7
|
24
24
|
License-File: LICENSE.txt
|
25
25
|
License-File: NOTICE.txt
|
26
|
-
Requires-Dist: flask
|
26
|
+
Requires-Dist: flask >=3.0.0
|
27
27
|
Requires-Dist: flask-compress >=1.11
|
28
28
|
Requires-Dist: pyzmq >=24.0.1
|
29
29
|
Requires-Dist: pexpect >=4.8.0
|