Commit 2f12f96daddca77e6380479cb7a7073b8fb0151d
1 parent
afe539cb
Exists in
dev
add Yaml editor for observatory configuration file
Showing
3 changed files
with
174 additions
and
11 deletions
Show diff stats
src/core/pyros_django/obsconfig/configpyros.py
... | ... | @@ -15,7 +15,9 @@ class ConfigPyros: |
15 | 15 | devices = None |
16 | 16 | computers = None |
17 | 17 | agents = None |
18 | + obs_config_file_content = None | |
18 | 19 | obs_config_path = os.environ["PATH_TO_OBSCONF_FOLDER"] |
20 | + errors = None | |
19 | 21 | def verify_if_pickle_needs_to_be_updated(self,observatory_config_file)->bool: |
20 | 22 | """[summary] |
21 | 23 | |
... | ... | @@ -25,7 +27,8 @@ class ConfigPyros: |
25 | 27 | Returns: |
26 | 28 | bool: [description] |
27 | 29 | """ |
28 | - self.CONFIG_PATH = os.path.dirname(observatory_config_file)+"/" | |
30 | + #self.CONFIG_PATH = os.path.dirname(observatory_config_file)+"/" | |
31 | + self.CONFIG_PATH = self.obs_config_path | |
29 | 32 | if os.path.isfile(self.CONFIG_PATH+self.pickle_file) == False: |
30 | 33 | return True |
31 | 34 | else: |
... | ... | @@ -35,7 +38,7 @@ class ConfigPyros: |
35 | 38 | obs_config = self.read_and_check_config_file(observatory_config_file) |
36 | 39 | if obs_config_mtime > pickle_file_mtime: |
37 | 40 | # create obs file (yaml) from pickle["obsconfig"] with date of pickle within history folder-> nom ficher + année + mois + jour + datetime (avec secondes) -> YYYY/MM/DD H:m:s |
38 | - pickle_datetime = datetime.fromtimestamp(pickle_file_mtime).strftime("%Y%m%d_%H%M%S") | |
41 | + pickle_datetime = datetime.utcfromtimestamp(pickle_file_mtime).strftime("%Y%m%d_%H%M%S") | |
39 | 42 | # Create history folder if doesn't exist |
40 | 43 | Path(self.obs_config_path+"/history/").mkdir(parents=True, exist_ok=True) |
41 | 44 | file_name = f"{self.obs_config_path}/history/observatory_{pickle_datetime}.yml" |
... | ... | @@ -62,6 +65,7 @@ class ConfigPyros: |
62 | 65 | |
63 | 66 | def load(self,observatory_config_file): |
64 | 67 | does_pickle_needs_to_be_updated = self.verify_if_pickle_needs_to_be_updated(observatory_config_file) |
68 | + print(f"OBS CONFIG FILE {observatory_config_file}") | |
65 | 69 | if os.path.isfile(self.CONFIG_PATH+self.pickle_file) and does_pickle_needs_to_be_updated == False: |
66 | 70 | print("Reading pickle file") |
67 | 71 | try: |
... | ... | @@ -79,6 +83,7 @@ class ConfigPyros: |
79 | 83 | self.computers = pickle_dict["computers"] |
80 | 84 | self.devices = pickle_dict["devices"] |
81 | 85 | self.devices_links = pickle_dict["devices_links"] |
86 | + self.obs_config_file_content = pickle_dict["obs_config_file_content"] | |
82 | 87 | else: |
83 | 88 | print("Pickle file needs to be created or updated") |
84 | 89 | pickle_dict = {} |
... | ... | @@ -88,6 +93,7 @@ class ConfigPyros: |
88 | 93 | pickle_dict["devices"] = self.get_devices() |
89 | 94 | pickle_dict["computers"] = self.get_computers() |
90 | 95 | pickle_dict["devices_links"] = self.devices_links |
96 | + pickle_dict["obs_config_file_content"] = self.read_and_check_config_file(observatory_config_file) | |
91 | 97 | print("Writing pickle file") |
92 | 98 | pickle.dump(pickle_dict,open(self.CONFIG_PATH+self.pickle_file,"wb")) |
93 | 99 | |
... | ... | @@ -113,16 +119,52 @@ class ConfigPyros: |
113 | 119 | print(f"{yaml_file} can't be accessed, waiting for availability") |
114 | 120 | time.sleep(0.5) |
115 | 121 | |
116 | - c = pykwalify.core.Core(source_file=yaml_file, schema_files=[schema_file]) | |
122 | + c = pykwalify.core.Core(source_file=yaml_file, schema_files=[self.SCHEMA_PATH+schema_file]) | |
117 | 123 | return c.validate(raise_exception=True) |
118 | 124 | except SchemaError: |
119 | 125 | for error in c.errors: |
120 | 126 | print("Error :",str(error).split(". Path")[0]) |
121 | 127 | print("Path to error :",error.path) |
128 | + self.errors = c.errors | |
122 | 129 | return None |
123 | 130 | except IOError: |
124 | 131 | print("Error when reading the observatory config file") |
125 | 132 | |
133 | + @staticmethod | |
134 | + def check_config(yaml_file:str,schema_file:str)->any: | |
135 | + """ | |
136 | + Check if yaml_file is valid for the schema_file and return a boolean or list of errors according the schema | |
137 | + | |
138 | + Args: | |
139 | + yaml_file (str): Path to the config_file to be validated | |
140 | + schema_file (str): Path to the schema file | |
141 | + | |
142 | + Returns: | |
143 | + any: boolean (True) if the configuration is valid according the schema or a list of error otherwise | |
144 | + """ | |
145 | + # disable pykwalify error to clean the output | |
146 | + logging.disable(logging.ERROR) | |
147 | + try: | |
148 | + can_yaml_file_be_read = False | |
149 | + while can_yaml_file_be_read != True: | |
150 | + if os.access(yaml_file, os.R_OK): | |
151 | + can_yaml_file_be_read = True | |
152 | + else: | |
153 | + print(f"{yaml_file} can't be accessed, waiting for availability") | |
154 | + time.sleep(0.5) | |
155 | + | |
156 | + c = pykwalify.core.Core(source_file=yaml_file, schema_files=[schema_file]) | |
157 | + c.validate(raise_exception=True) | |
158 | + return True | |
159 | + except SchemaError: | |
160 | + for error in c.errors: | |
161 | + print("Error :",str(error).split(". Path")[0]) | |
162 | + print("Path to error :",error.path) | |
163 | + | |
164 | + return c.errors | |
165 | + except IOError: | |
166 | + print("Error when reading the observatory config file") | |
167 | + | |
126 | 168 | def read_and_check_config_file(self,yaml_file:str)->dict: |
127 | 169 | """ |
128 | 170 | Read the schema key of the config file to retrieve schema name and proceed to the checking of that config file |
... | ... | @@ -148,10 +190,10 @@ class ConfigPyros: |
148 | 190 | config_file = yaml.safe_load(stream) |
149 | 191 | |
150 | 192 | self.SCHEMA_PATH = os.path.join(os.environ["DJANGO_PATH"],"../../../config/schemas/") |
151 | - self.CONFIG_PATH = os.path.dirname(yaml_file)+"/" | |
193 | + self.CONFIG_PATH = self.obs_config_path | |
152 | 194 | #self.COMPONENT_PATH = os.path.join(os.path.abspath(os.path.dirname(yaml_file)),"../../../config/components/") |
153 | 195 | #self.GENERIC_DEVICES_PATH = os.path.join(os.path.abspath(os.path.dirname(yaml_file)),"../../../config/devices/") |
154 | - result = self.check_and_return_config(yaml_file,self.SCHEMA_PATH+config_file["schema"]) | |
196 | + result = self.check_and_return_config(yaml_file,config_file["schema"]) | |
155 | 197 | if result == None: |
156 | 198 | print("Error when reading and validating config file, please check the errors right above") |
157 | 199 | exit(1) |
... | ... | @@ -261,17 +303,14 @@ class ConfigPyros: |
261 | 303 | with open(config_file_name, 'r') as stream: |
262 | 304 | config_file = yaml.safe_load(stream) |
263 | 305 | # if we're reading a generic device configuration, the path to get the schema is different than usual |
264 | - if is_generic: | |
265 | - self.SCHEMA_PATH =os.path.join(os.environ["DJANGO_PATH"],"../../../config/schemas/") | |
266 | - else: | |
267 | - self.SCHEMA_PATH = os.path.join(os.environ["DJANGO_PATH"],"../../../config/schemas/") | |
306 | + self.SCHEMA_PATH =os.path.join(os.environ["DJANGO_PATH"],"../../../config/schemas/") | |
268 | 307 | |
269 | 308 | # read and verify that the device configuration match the schema |
270 | - result = self.check_and_return_config(config_file_name,self.SCHEMA_PATH+config_file["schema"]) | |
309 | + result = self.check_and_return_config(config_file_name,config_file["schema"]) | |
271 | 310 | # if the configuration didn't match the schema or had an error when reading the file |
272 | 311 | if result == None: |
273 | 312 | print("Error when reading and validating config file, please check the errors right above") |
274 | - exit(-1) | |
313 | + exit(1) | |
275 | 314 | else: |
276 | 315 | # the configuration is valid |
277 | 316 | # storing DEVICE key in device (DEVICE can contains : attributes of device, capabilities, attached devices) | ... | ... |
src/core/pyros_django/obsconfig/templates/obsconfig/edit_config.html
0 → 100644
... | ... | @@ -0,0 +1,121 @@ |
1 | +{% extends "base.html" %} | |
2 | +{% load tags %} | |
3 | +{% load static %} | |
4 | +{% block content %} | |
5 | + | |
6 | +<style type="text/css" media="screen"> | |
7 | + #editor { | |
8 | + position: absolute; | |
9 | + top: 10vh; | |
10 | + right: 25vw; | |
11 | + bottom: 5vh; | |
12 | + left: 15vw; | |
13 | + } | |
14 | + #send_file{ | |
15 | + position:absolute; | |
16 | + left:80vw; | |
17 | + top: 10vh; | |
18 | + } | |
19 | + #check_config{ | |
20 | + position:absolute; | |
21 | + left:80vw; | |
22 | + top: 20vh; | |
23 | + } | |
24 | + #save{ | |
25 | + position:absolute; | |
26 | + left:80vw; | |
27 | + top: 30vh; | |
28 | + } | |
29 | + #message{ | |
30 | + position:absolute; | |
31 | + left:80vw; | |
32 | + top: 25vh; | |
33 | + } | |
34 | + .red{ | |
35 | + color:red; | |
36 | + } | |
37 | +</style> | |
38 | + | |
39 | +<div id="editor"> | |
40 | +{{ config_file }} | |
41 | +</div> | |
42 | +<div id="send_file"> | |
43 | +<h3>Import from file</h3> | |
44 | + <input type="file" name="inputfile" | |
45 | + id="inputfile"> | |
46 | + | |
47 | +</div> | |
48 | +<div> | |
49 | +{% csrf_token %} | |
50 | +<button id="check_config">Check configuration</button> | |
51 | + | |
52 | +<p id="message"></p> | |
53 | +</div> | |
54 | +<div id="save" class="hidden"> | |
55 | +<label> File name: (without ".yml" extension)</label> | |
56 | + <input id="file_name"></input> | |
57 | + <button id="save_btn">Save</button> | |
58 | +<p id="save_message"></p> | |
59 | +</div> | |
60 | + | |
61 | +<script src="{% static "/js/ace/src/ace.js" %}"></script> | |
62 | +<script> | |
63 | + var editor = ace.edit("editor"); | |
64 | + editor.setTheme("ace/theme/eclipse"); | |
65 | + editor.session.setMode("ace/mode/yaml"); | |
66 | + | |
67 | + document.getElementById('inputfile') | |
68 | + .addEventListener('change', function() { | |
69 | + // loading default file (current observatory configuration file) | |
70 | + var fr=new FileReader(); | |
71 | + fr.onload=function(){ | |
72 | + editor.setValue(fr.result); | |
73 | + } | |
74 | + | |
75 | + fr.readAsText(this.files[0]); | |
76 | + editor.resize(); | |
77 | + | |
78 | + }); | |
79 | + document.querySelector("#check_config").addEventListener('click',function(){ | |
80 | + //console.log(editor.getValue()); | |
81 | + csrf_value = $("[name='csrfmiddlewaretoken']").val(); | |
82 | + $.post('{% url 'verify_config' %}', { "config": editor.getValue(), csrfmiddlewaretoken: csrf_value }, function(data,status) { | |
83 | + console.log(data); | |
84 | + console.log(status); | |
85 | + if(data["is_valid"] == true){ | |
86 | + $("#message").empty().attr('class', 'normal').append("Valid configuration, do you want to save it ?"); | |
87 | + $("#save").toggleClass('hidden'); | |
88 | + } | |
89 | + else{ | |
90 | + $("#save").attr("class",'hidden') | |
91 | + $("#message").empty().attr('class', 'red').append("Invalid configruation, see the errors below:"); | |
92 | + for(error of data["message"] ){ | |
93 | + for(message of error){ | |
94 | + $("#message").append("<p>"+message+"</p>"); | |
95 | + | |
96 | + } | |
97 | + | |
98 | + } | |
99 | + } | |
100 | + }); | |
101 | + }); | |
102 | + document.querySelector("#save_btn").addEventListener('click',function(){ | |
103 | + /* | |
104 | + file_name = document.querySelector("#file_name").value; | |
105 | + if(file_name.length == 0){ | |
106 | + $("#save_message").empty().append("<p>Please name your file</p>"); | |
107 | + return -1 | |
108 | + }*/ | |
109 | + //$.post('{% url 'save_config' %}', { "config": editor.getValue(),"file_name":file_name, csrfmiddlewaretoken: csrf_value }, function(data,status) { | |
110 | + $.post('{% url 'save_config' %}', { "config": editor.getValue(), csrfmiddlewaretoken: csrf_value }, function(data,status) { | |
111 | + if(status == "success"){ | |
112 | + $("#save_message").empty().append("<p>Configuration saved !</p>"); | |
113 | + } | |
114 | + else{ | |
115 | + $("#save_message").empty().append("<p class='red'>Error while trying to save the configuration file</p>"); | |
116 | + } | |
117 | + }); | |
118 | + | |
119 | + }); | |
120 | + </script> | |
121 | +{% endblock %} | |
0 | 122 | \ No newline at end of file | ... | ... |
src/core/pyros_django/obsconfig/urls.py
... | ... | @@ -9,4 +9,7 @@ urlpatterns = [ |
9 | 9 | path('unit_hardware_configuration/<str:unit_name>', views.unit_hardware_configuration, name='unit_hardware_configuration'), |
10 | 10 | path('computer_details/<str:computer_name>', views.computer_details, name='computer_details'), |
11 | 11 | path('device_details/<str:device_name>', views.device_details, name='device_details'), |
12 | + path('edit_config',views.edit_config,name="edit_config"), | |
13 | + path('verify_config',views.verify_config,name="verify_config"), | |
14 | + path('save_config',views.save_config,name="save_config") | |
12 | 15 | ] |
13 | 16 | \ No newline at end of file | ... | ... |