Browse Source

Move trace choice to table

Arkadiusz Ryś 2 years ago
parent
commit
adb13f131d

+ 18 - 0
.editorconfig

@@ -0,0 +1,18 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 4
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.{css,html,yml,yaml,js,xml}]
+indent_size = 2
+
+[{*.log,LICENSE}]
+insert_final_newline = false
+
+[*.rst]
+indent_size = 3

+ 4 - 10
src/main/java/ua/be/wee/WeeApplication.java

@@ -5,14 +5,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
 
 @SpringBootApplication
 public class WeeApplication {
-
-	
-	public static void main(String[] args) {
-		System.setProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "true");
-
-		
-		SpringApplication.run(WeeApplication.class, args);
-		
-	}
-
+    public static void main(String[] args) {
+        System.setProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "true");
+        SpringApplication.run(WeeApplication.class, args);
+    }
 }

+ 39 - 42
src/main/java/ua/be/wee/controller/EnactmentControllerMVC.java

@@ -46,13 +46,13 @@ import ua.be.wee.service.FileStorageService;
 public class EnactmentControllerMVC {
 
 	@Autowired private EnactmentController controller;
-	
+
 	@Autowired
 	private PTController ptController;
-  
+
 	@Autowired
 	private Environment env;
-	
+
 	@Autowired
 	FileStorageService storageService;
 
@@ -89,7 +89,7 @@ public class EnactmentControllerMVC {
 			return "index";
 		}
     }
-    
+
     @RequestMapping("/gettraces")
     public String getTraces(Model model, @RequestParam String iri, HttpServletRequest request) throws Exception {
     	List<StartTraceEvent> activeTraces = ptController.getActiveTraces(iri);
@@ -103,8 +103,8 @@ public class EnactmentControllerMVC {
 		model.addAttribute("current", iri);
         return "pms";
     }
-    
-    
+
+
     @RequestMapping(value="/enactpm", method=RequestMethod.POST, params="action=new")
     public String getPMtoEnact(Model model, @RequestParam String pmiri, HttpServletRequest request) throws Exception {
     	if ((pmiri.equals("select PM"))) {
@@ -114,7 +114,7 @@ public class EnactmentControllerMVC {
     		model.addAttribute("current", "1");
 			return "pms";
 		} else {
-			PM pm = controller.getPM(pmiri);			
+			PM pm = controller.getPM(pmiri);
 			PT trace = controller.createTrace(pm);
 			List<Pair<String,String>> iris = controller.findNextNodes(pm.getInitial().getIri(), trace.getIri());
 			List<PMTrigger> acts = findElements(pm, iris);
@@ -122,23 +122,23 @@ public class EnactmentControllerMVC {
 			request.getSession().setAttribute("pm", pm);
 			request.getSession().setAttribute("trace", trace);
 			request.getSession().setAttribute("acts",acts);
-			request.getSession().setAttribute("endacts",new ArrayList<Node>());
+			request.getSession().setAttribute("endacts",new ArrayList<Node>()); // TODO Is this a typo?
 			model.addAttribute("arts", null);
 			model.addAttribute("current", "1");
 	        return "enact";
 		}
     }
-    
+
     @RequestMapping(value="/enactpm", method=RequestMethod.POST, params="action=continue")
     public String continueEnact(Model model, @RequestParam String pmiri, @RequestParam String contTrace, HttpServletRequest request) throws Exception {
-    	if ((pmiri.equals("select PM")) || contTrace != null && contTrace.equals("Select Trace")) {
-			model.addAttribute("error", true);
+        if ((pmiri.equals("select PM")) || contTrace != null && contTrace.equals("Select Trace")) {
+            model.addAttribute("error", true);
 			model.addAttribute("pms", controller.getAllPMs());
 			model.addAttribute("traces",null);
     		model.addAttribute("current", "1");
 			return "pms";
 		} else {
-			PM pm = controller.getPM(pmiri);			
+            PM pm = controller.getPM(pmiri);
 			List<Event> events = ptController.getEvents(contTrace);
 			PT trace = new PT();
 			trace.setEvents(events);
@@ -149,7 +149,6 @@ public class EnactmentControllerMVC {
 				List<Pair<String,String>> iris = controller.findNextNodes(pm.getInitial().getIri(), trace.getIri());
 				List<PMTrigger> acts = findElements(pm, iris);
 				request.getSession().setAttribute("acts",acts);
-				
 			} else {
 				endActs = findEndActs(events);
 				List<PMTrigger> acts = findStartActs(trace);
@@ -158,10 +157,8 @@ public class EnactmentControllerMVC {
 					List<Pair<String,String>> iris = controller.findNextNodes(last.getRelatesTo().getIri(), trace.getIri());
 					acts.addAll(findElements(pm, iris));
 				}
-				
 				request.getSession().setAttribute("acts",acts);
-				
-			}			
+			}
 			model.addAttribute("error", false);
 			request.getSession().setAttribute("pm", pm);
 			request.getSession().setAttribute("trace", trace);
@@ -171,7 +168,7 @@ public class EnactmentControllerMVC {
 	        return "enact";
 		}
     }
-    
+
 
 	private List<PMTrigger> findStartActs(PT trace) throws Exception {
 		PM pmEnacted = trace.getPmEnacted();
@@ -189,10 +186,10 @@ public class EnactmentControllerMVC {
 						}
 					}
 					List<String> consider = checkTrace(trace,actIris);
-						
+
 					for (Iterator<Pair<String,String>> iterator = findNextNodes.iterator(); iterator.hasNext();) {
 						Pair<String,String> pair = iterator.next();
-						
+
 						if (pair.getSnd() == null) {
 							findNextNodes.remove(pair);
 						} else {
@@ -208,9 +205,9 @@ public class EnactmentControllerMVC {
 							}
 						}
 					}
-					
+
 					result.addAll(findElements(trace.getPmEnacted(), findNextNodes));
-					
+
 				}
 			}
 		}
@@ -233,7 +230,7 @@ public class EnactmentControllerMVC {
 			}
 			pairs.add(new Pair<String, Integer>(iri, count));
 		}
-		
+
 		List<String> result = new ArrayList<String>();
 		for (Pair<String, Integer> pair : pairs) {
 			if (pair.getSnd() < max) {
@@ -241,7 +238,7 @@ public class EnactmentControllerMVC {
 			}
 		}
 		return result;
-		
+
 	}
 
 	private List<Node> findEndActs(List<Event> events) {
@@ -289,11 +286,11 @@ public class EnactmentControllerMVC {
 		} else {
 			return "error";
 		}
-    	
-    	
+
+
     }
-    
-    
+
+
 
     @PostMapping("/endAct")
     public String endActivityWithArtifacts(Model model, @RequestParam String port, @RequestParam String activity, HttpServletRequest request) throws Exception {
@@ -310,7 +307,7 @@ public class EnactmentControllerMVC {
 				break;
 			}
 		}
-    	
+
     	List<Artifact> arts = act.getOutputs();
     	List<TraceArtifact> traceArts = new ArrayList<TraceArtifact>();
     	for (Artifact artifact : arts) {
@@ -323,16 +320,16 @@ public class EnactmentControllerMVC {
 					traceArts.add(tArt);
 					storageService.save(part);
 				}
-			}  
+			}
 		}
-    	
+
     	controller.addEndEvent(pt,traceArts,p);
     	for (Node activ : endacts) {
 			if (activ.getIri().equals(act.getIri())) {
 				endacts.remove(activ);
 				break;
 			}
-		}	
+		}
 
     	List<Pair<String,String>> iris = controller.findNextNodes(p.getIri(), pt.getIri());
         acts.addAll(findElements(pm, iris));
@@ -343,7 +340,7 @@ public class EnactmentControllerMVC {
 		model.addAttribute("current", "1");
     	return "enact";
     }
-    
+
     @RequestMapping("/endselect")
     public String selectActivityToEnd(Model model, @RequestParam String iri, HttpServletRequest request) throws Exception {
     	PM pm = (PM)request.getSession().getAttribute("pm");
@@ -352,17 +349,17 @@ public class EnactmentControllerMVC {
         		Activity act = (Activity)pm.getNode(iri);
             	model.addAttribute("act", pm.getNode(iri));
             	model.addAttribute("arts", act.getOutputs());
-			} else { 
+			} else {
 				model.addAttribute("endBool", true);
 			}
-        	
+
 		} else {
 			model.addAttribute("arts", null);
 		}
 
     	return "enactEnd";
     }
-    
+
     @RequestMapping("/inarts")
     public String selectActivity(Model model, @RequestParam String iri, HttpServletRequest request) throws Exception {
     	PT pt = (PT)request.getSession().getAttribute("trace");
@@ -393,21 +390,21 @@ public class EnactmentControllerMVC {
             	//model.addAttribute("arts", arts);
             	request.getSession().setAttribute("arts", arts);
             	model.addAttribute("storageURL", env.getProperty("storageURL"));
-			} else { 
+			} else {
 				model.addAttribute("endBool", true);
 			}
-        	
+
 		} else {
 			request.getSession().setAttribute("arts", null);
 		}
     	model.addAttribute("current", iri);
     	return "enact";
     }
-    
+
 
     // Helper methods
-    
-    
+
+
     private List<PMTrigger> findElements(PM pm, List<Pair<String, String>> iris) {
 		List<PMTrigger> acts = new ArrayList<PMTrigger>();
 		for (Pair<String,String> pair : iris) {
@@ -430,7 +427,7 @@ public class EnactmentControllerMVC {
 		}
 		return acts;
 	}
-    
+
     private PMTrigger findPMTrigger(List<PMTrigger> acts, String iri) {
 		for (PMTrigger pmTrigger : acts) {
 			if (pmTrigger.getIri().equals(iri)) {
@@ -450,5 +447,5 @@ public class EnactmentControllerMVC {
 
 	}
 
-	
+
 }

+ 6 - 6
src/main/resources/static/favicon.svg

@@ -6,19 +6,19 @@
     </defs>
     <g fill="none" fill-rule="evenodd" transform="translate(2 1)">
         <g transform="translate(3 3)">
-            <mask id="share-b" fill="#ffffff">
+            <mask id="share-b" fill="#00c4a7">
                 <use xlink:href="#share-a"/>
             </mask>
-            <use fill="#fff" xlink:href="#share-a"/>
-            <g fill="#fff" mask="url(#share-b)">
+            <use fill="#00c4a7" xlink:href="#share-a"/>
+            <g fill="#00c4a7" mask="url(#share-b)">
                 <rect width="24" height="24" transform="translate(-5 -4)"/>
             </g>
         </g>
-        <mask id="share-d" fill="#ffffff">
+        <mask id="share-d" fill="#00c4a7">
             <use xlink:href="#share-c"/>
         </mask>
-        <use fill="#000000" fill-rule="nonzero" xlink:href="#share-c"/>
-        <g fill="#fff" mask="url(#share-d)">
+        <use fill="#00c4a7" fill-rule="nonzero" xlink:href="#share-c"/>
+        <g fill="#000" mask="url(#share-d)">
             <rect width="24" height="24" transform="translate(-2 -1)"/>
         </g>
     </g>

+ 100 - 50
src/main/resources/static/main.js

@@ -1,27 +1,27 @@
-function showDiv(select){
-    let hiddenDiv = document.getElementById('hidden_div');
-    if (select.value != 1) {
-        hiddenDiv.classList.remove('is-hidden');
-    } else{
-        hiddenDiv.classList.add('is-hidden');
-    }
+function showDiv(select) {
+  let hiddenDiv = document.getElementById('hidden_div');
+  if (select.value != 1) {
+    hiddenDiv.classList.remove('is-hidden');
+  } else {
+    hiddenDiv.classList.add('is-hidden');
+  }
 }
 
 function play() {
-           var audio = document.getElementById("audio");
-    		var img = document.getElementById("img");
-    
-    		function play() {
-    			  audio.play();
-    		}
-    
-    		function stop() {
-      			audio.pause();
-    		}
-    
-    		img.addEventListener('click', play);
-    		img.addEventListener('mouseover', play);
-    		img.addEventListener('mouseout', stop);
+  const audio = document.getElementById("audio");
+  const img = document.getElementById("img");
+
+  function play() {
+    audio.play();
+  }
+
+  function stop() {
+    audio.pause();
+  }
+
+  img.addEventListener('click', play);
+  img.addEventListener('mouseover', play);
+  img.addEventListener('mouseout', stop);
 }
 
 // document.addEventListener('DOMContentLoaded', () => {
@@ -33,49 +33,99 @@ function play() {
 // });
 
 document.addEventListener('DOMContentLoaded', () => {
-    // Get all "navbar-burger" elements
-    const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
+  // Get all "navbar-burger" elements
+  const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
 
-    // Add a click event on each of them
-    $navbarBurgers.forEach( el => {
-        el.addEventListener('click', () => {
+  // Add a click event on each of them
+  $navbarBurgers.forEach(el => {
+    el.addEventListener('click', () => {
 
-            // Get the target from the "data-target" attribute
-            const target = el.dataset.target;
-            const $target = document.getElementById(target);
+      // Get the target from the "data-target" attribute
+      const target = el.dataset.target;
+      const $target = document.getElementById(target);
 
-            // Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
-            el.classList.toggle('is-active');
-            $target.classList.toggle('is-active');
-        });
+      // Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
+      el.classList.toggle('is-active');
+      $target.classList.toggle('is-active');
     });
+  });
 });
 
 document.addEventListener('DOMContentLoaded', () => {
-    (document.querySelectorAll('.notification .delete') || []).forEach(($delete) => {
-        const $notification = $delete.parentNode;
-        $delete.addEventListener('click', () => {
-            $notification.parentNode.removeChild($notification);
-        });
+  (document.querySelectorAll('.notification .delete') || []).forEach(($delete) => {
+    const $notification = $delete.parentNode;
+    $delete.addEventListener('click', () => {
+      $notification.parentNode.removeChild($notification);
     });
+  });
 });
 
 document.addEventListener('DOMContentLoaded', () => {
-    (document.querySelectorAll('#endpointURL') || []).forEach(($urlInput) => {
-        $urlInput.addEventListener('input', (e) => {
-            window.localStorage.setItem("uri", e.target.value);
-            console.log('Storing uri input ' + e.target.value);
-        });
+  (document.querySelectorAll('#endpointURL') || []).forEach(($urlInput) => {
+    $urlInput.addEventListener('input', (e) => {
+      window.localStorage.setItem("uri", e.target.value);
+      console.log('Storing uri input ' + e.target.value);
     });
+  });
 });
 
 document.addEventListener('DOMContentLoaded', () => {
-    let data = window.localStorage.getItem("uri");
-    if (data) {
-        (document.querySelectorAll('#endpointURL') || []).forEach(($urlInput) => {
-            $urlInput.setAttribute('value', data);
-            console.log('Setting uri input to ' + data);
-        });
-    }
+  let data = window.localStorage.getItem("uri");
+  if (data) {
+    (document.querySelectorAll('#endpointURL') || []).forEach(($urlInput) => {
+      $urlInput.setAttribute('value', data);
+      console.log('Setting uri input to ' + data);
+    });
+  }
 });
 
+function sendFormData(uri, pm_iri, pt_iri) {
+  console.log(pm_iri);
+  console.log(pt_iri);
+  // Get value of dropDownList in pm.html
+  let formData = new FormData();
+  formData.append('action', 'continue');
+  formData.append('pmiri', pm_iri);
+  formData.append('contTrace', pt_iri);
+  fetch(uri, {method: 'POST', body: formData, redirect: "follow"}).then(r => console.log(r));
+}
+
+function sendHiddenForm(uri, pm_iri, pt_iri) {
+  const hidden_form = document.createElement('form');
+  hidden_form.method = 'post';
+  hidden_form.action = uri;
+
+  const hidden_pm_input = document.createElement('input');
+  hidden_pm_input.type = 'hidden';
+  hidden_pm_input.name = 'pmiri';
+  hidden_pm_input.value = pm_iri;
+
+  const hidden_pt_input = document.createElement('input');
+  hidden_pt_input.type = 'hidden';
+  hidden_pt_input.name = 'contTrace';
+  hidden_pt_input.value = pt_iri;
+
+  const action_input = document.createElement('input');
+  action_input.type = 'hidden';
+  action_input.name = 'action';
+  action_input.value = 'continue';
+
+  hidden_form.appendChild(hidden_pm_input);
+  hidden_form.appendChild(hidden_pt_input);
+  hidden_form.appendChild(action_input);
+
+  document.body.appendChild(hidden_form);
+  hidden_form.submit();
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+  (document.querySelectorAll('.trace-continuation-row') || []).forEach(($row) => {
+    console.log($row);
+    const pm_dropdown_value = document.getElementById('dropDownList').value;
+    const button = $row.getElementsByTagName('button')[0];
+    button.addEventListener('click', (e) => {
+      // sendFormData($row.dataset.action, pm_dropdown_value, $row.dataset.iri)
+      sendHiddenForm($row.dataset.action, pm_dropdown_value, $row.dataset.iri)
+    });
+  });
+});

+ 10 - 11
src/main/resources/templates/base.html

@@ -1,6 +1,5 @@
 <!DOCTYPE html>
 <html lang="en" xmlns:th="http://www.thymeleaf.org" th:fragment="layout(title, main)">
-<!--<html lang="en" xmlns:th="http://www.thymeleaf.org" th:fragment="layout">-->
 <head>
   <meta charset="UTF-8"/>
   <title th:replace="${title}"></title>
@@ -9,14 +8,14 @@
   <link href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css" rel="stylesheet">
   <link rel="stylesheet" type="text/css" href="../static/reset.css" th:href="@{/reset.css}"/>
   <link rel="stylesheet" type="text/css" href="../static/style.css" th:href="@{/style.css}"/>
-  <link rel="icon" href="../static/favicon.svg" th:href="@{/favicon.svg}"/>
+  <link rel="icon" href="../static/favicon.svg" th:href="@{/icon.svg}"/>
 </head>
 <body>
 <header>
   <nav class="navbar" role="navigation" aria-label="main navigation">
     <div class="navbar-brand">
       <a class="navbar-item" href="/">
-<!--        <img src="https://bulma.io/images/bulma-logo.png" width="112" height="28">-->
+        <img th:src="@{/icon.svg}" width="28" height="28">
         <h1 class="is-size-3">Workflow Enactment Engine</h1>
       </a>
       <a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="navigation-menu">
@@ -28,11 +27,12 @@
 
     <div id="navigation-menu" class="navbar-menu">
       <div class="navbar-start">
-        <a class="navbar-item" href="/" >Home</a>
+        <!--        <a class="navbar-item" href="/" >Home</a>-->
         <div class="navbar-item has-dropdown is-hoverable">
           <a class="navbar-link">Enactment</a>
           <div class="navbar-dropdown">
-            <a href="https://msdl.uantwerpen.be/git/lucasalbertins/wee/src/master/README.md" class="navbar-item">About</a>
+            <a href="https://msdl.uantwerpen.be/git/lucasalbertins/wee/src/master/README.md"
+               class="navbar-item">About</a>
             <hr class="navbar-divider">
             <a href="https://msdl.uantwerpen.be/git/lucasalbertins/wee/issues" class="navbar-item">Report an issue</a>
           </div>
@@ -42,25 +42,24 @@
       <div class="navbar-end">
         <div class="navbar-item">
           <div class="buttons">
-            <a href="https://msdl.uantwerpen.be/git/lucasalbertins/wee" class="button is-primary">
+            <a href="https://msdl.uantwerpen.be/git/lucasalbertins/wee/wiki" class="button is-primary">
               <strong>?</strong>
             </a>
           </div>
         </div>
       </div>
     </div>
-    <!--  <button id="toggle">Theme</button>-->
   </nav>
 </header>
 <main th:replace="${main}"></main>
 <footer>
   <section>
-<!--  https://stackoverflow.com/questions/30574093/how-to-implement-breadcrumbs-in-spring-mvc-thymeleaf  -->
     <nav class="breadcrumb pl-5 pr-5" aria-label="breadcrumbs">
       <ul>
-        <li><a href="#">WEE</a></li>
-        <li><a href="#">PM</a></li>
-        <li class="is-active"><a href="#" aria-current="page">Index</a></li>
+        <li th:each="entry : ${breadcrumbs}" th:classappend="${entry.isCurrent} ? 'is-active' : ''">
+          <a th:href="${entry.href}" th:unless="${entry.isCurrent}" th:text="${entry.label}"></a>
+          <span th:if="${entry.isCurrent}" th:text="${entry.label}"></span>
+        </li>
       </ul>
     </nav>
   </section>

+ 30 - 22
src/main/resources/templates/pms.html

@@ -1,44 +1,52 @@
 <!DOCTYPE html>
 <html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="base::layout(~{::title}, ~{::main})">
 <head>
-  <title>Process Model Enactment</title>
+  <title>Process Model Choice</title>
 </head>
 <body>
 <main>
   <section class="pt-6 pb-6 pl-5 pr-5">
-    <h2 class="is-size-3 is-capitalized">Wee: Workflow Enactment Engine</h2>
     <form class="pt-3" th:action="@{/enactpm}" method="post">
-      <p class="is-size-4">Choose the Process Model to be enacted:</p>
+      <p class="is-size-4">Choose the Process Model to be enacted</p>
+
       <div class="field has-addons">
         <div class="control is-expanded">
           <div class="select is-fullwidth">
-            <select id="dropDownList" name="pmiri" th:onchange="'window.location.href = \'' + @{/gettraces} + '?iri=\' + encodeURIComponent(this.value) ' ">
+            <select id="dropDownList" name="pmiri"
+                    th:onchange="'window.location.href = \'' + @{/gettraces} + '?iri=\' + encodeURIComponent(this.value) ' ">
               <option th:value="1">select PM</option>
-              <option th:each="pm: ${pms}" th:value="${pm.iri}" th:text="${pm}" th:selected="${pm.iri == current}"></option>
+              <option th:each="pm: ${pms}" th:value="${pm.iri}" th:text="${pm}"
+                      th:selected="${pm.iri == current}"></option>
             </select>
           </div>
         </div>
         <div class="control">
-          <button type="submit" class="button is-primary" name="action" value="new" >New Enactment</button>
+          <button type="submit" class="button is-primary" name="action" value="new">Start New Enactment</button>
         </div>
-        
       </div>
-      
-       <div class="field has-addons" th:if="${traces != null}">
-          <p class="is-size-6">Continue enactment:</p>
-           <div class="select is-fullwidth">
-          <select id="dropDownList" name="contTrace">
-              <option value="Select Trace">select trace</option>
-              <option th:each="trace: ${traces}" th:value="${trace.iri}" th:text="${trace.name} + ' started at ' + ${trace.timestampF}"></option>
-          </select>
-          </div>
-          <div class="control">
-          	<button type="submit" class="button is-primary" name="action" value="continue">Continue Enactment</button>
-      	  </div>    
-       </div>
-      
     </form>
+    <section th:if="${traces != null}">
+      <p>This Process Model has ongoing enactments. Do you wish to continue with a previously started enactment?</p>
+      <table class="table is-striped is-hoverable is-fullwidth">
+        <thead>
+        <tr>
+          <th>Name</th>
+          <th><abbr title="Internationalized Resource Identifier">IRI</abbr></th>
+          <th>Start Time</th>
+          <th></th>
+        </tr>
+        </thead>
+        <tbody>
+        <tr th:each="trace: ${traces}" class="trace-continuation-row" th:attr="data-name=${trace.name},data-iri=${trace.iri},data-timestamp=${trace.timestampF},data-action=@{/enactpm}">
+          <th th:text="${trace.name}"></th>
+          <td th:text="${trace.iri}"></td>
+          <td th:text="${trace.timestampF}"></td>
+          <td><button class="button is-primary is-small"><span class="icon is-small"><i class="fas fa-check"></i></span><span>Continue Enactment</span></button></td>
+        </tr>
+        </tbody>
+      </table>
+    </section>
   </section>
 </main>
 </body>
-</html>
+</html>