Commit 2f12f96daddca77e6380479cb7a7073b8fb0151d

Authored by Alexis Koralewski
1 parent afe539cb
Exists in dev

add Yaml editor for observatory configuration file

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
... ...