Added layout via bootstrap and fixed error handling on multi-result
Change-Id: I7db15bbf020c9076dfb8cbe9055da56c29827b3e
Signed-off-by: Dirk Fauth <Dirk.Fauth@de.bosch.com>
diff --git a/manager/pom.xml b/manager/pom.xml
index 551fab8..bec8421 100644
--- a/manager/pom.xml
+++ b/manager/pom.xml
@@ -65,6 +65,11 @@
<version>3.7.04</version>
</dependency>
+ <dependency>
+ <groupId>org.webjars</groupId>
+ <artifactId>bootstrap</artifactId>
+ <version>4.5.2</version>
+ </dependency>
</dependencies>
<build>
diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowController.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowController.java
index 2a30b83..8766f29 100644
--- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowController.java
+++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowController.java
@@ -29,6 +29,7 @@
import org.eclipse.app4mc.cloud.manager.storage.StorageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
+import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
@@ -103,6 +104,9 @@
ws.addSelectedService(selected);
+ // TODO init service configuration
+
+
// render the form view
return "workflow";
}
@@ -124,10 +128,27 @@
return "selectedServices :: servicesList";
}
+ @GetMapping("/messages")
+ public String getWorkflowMessages() {
+ // render the messagesList fragment contained in status.html
+ return "status :: messagesList";
+ }
+
+ @GetMapping("/errors")
+ public String getWorkflowErrors() {
+ // render the errorList fragment contained in status.html
+ return "status :: errorList";
+ }
+
+ @GetMapping("/results")
+ public String getWorkflowResults() {
+ // render the resultList fragment contained in status.html
+ return "status :: resultList";
+ }
+
@PostMapping("/workflow")
public String handleFileUpload(
@RequestParam("file") MultipartFile file,
- @RequestParam(name = "services", required = false) String[] services,
@RequestParam(name = "profiles", required = false) String[] validationProfiles,
Model model,
@ModelAttribute WorkflowStatus ws) {
@@ -177,17 +198,28 @@
public ResponseEntity<Resource> serveFile(
@PathVariable String uuid,
@PathVariable String servicepath,
- @PathVariable String filename) {
+ @PathVariable String filename,
+ @RequestParam(name = "download", required = false) String download) {
Resource file = storageService.loadAsResource(uuid, servicepath, filename);
+ if (!StringUtils.isEmpty(download) && "true".equals(download)) {
+ return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,
+ "attachment; filename=\"" + file.getFilename() + "\"").body(file);
+ }
+
if (filename.endsWith("html")) {
return ResponseEntity.ok().contentType(MediaType.TEXT_HTML).body(file);
+ } else if (filename.endsWith("pdf")) {
+ return ResponseEntity.ok().contentType(MediaType.APPLICATION_PDF).body(file);
+ } else if (filename.endsWith("gif")) {
+ return ResponseEntity.ok().contentType(MediaType.IMAGE_GIF).body(file);
+ } else if (filename.endsWith("jpg") || filename.endsWith("jpeg")) {
+ return ResponseEntity.ok().contentType(MediaType.IMAGE_JPEG).body(file);
+ } else if (filename.endsWith("png")) {
+ return ResponseEntity.ok().contentType(MediaType.IMAGE_PNG).body(file);
}
return ResponseEntity.ok().contentType(MediaType.TEXT_PLAIN).body(file);
-
-// return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,
-// "attachment; filename=\"" + file.getFilename() + "\"").body(file);
}
@GetMapping("/{uuid}/delete")
@@ -293,37 +325,39 @@
// TODO check http status 302
// check the response result code
- if (resultResponse.getStatus() == HttpStatus.SC_OK) {
- Path migrationResult = resultResponse.getBody().toPath();
- String filename = getFilenameFromHeader(resultResponse.getHeaders().getFirst("Content-Disposition"));
- if (filename != null) {
- migrationResult = Files.move(migrationResult, migrationSubDir.resolve(filename));
- }
-
- String resultName = resultResponse.getHeaders().getFirst("x-app4mc-result-name");
- if (StringUtils.isEmpty(resultName)) {
- resultName = serviceName;
- }
-
- workflowStatus.addMessage(resultName + " result downloaded");
-
- workflowStatus.addResult(
- resultName + " Result",
- MvcUriComponentsBuilder.fromMethodName(
- WorkflowController.class,
- "serveFile",
- workflowStatus.getUuid(),
- "_" + serviceName.toLowerCase(),
- migrationResult.getFileName().toString()).build().toUri().toString());
-
- // extract header information if result should be used in workflow
- String useResult = resultResponse.getHeaders().getFirst("x-app4mc-use-result");
- if (useResult == null || !useResult.toLowerCase().equals("false")) {
- // the result should be used in the workflow
- result = migrationResult;
- }
- } else {
+ Path migrationResult = resultResponse.getBody().toPath();
+ String filename = getFilenameFromHeader(resultResponse.getHeaders().getFirst("Content-Disposition"));
+ if (filename != null) {
+ migrationResult = Files.move(migrationResult, migrationSubDir.resolve(filename));
+ }
+
+ String resultName = resultResponse.getHeaders().getFirst("x-app4mc-result-name");
+ if (StringUtils.isEmpty(resultName)) {
+ resultName = serviceName;
+ }
+
+ workflowStatus.addMessage(resultName + " result downloaded");
+
+ workflowStatus.addResult(
+ resultName + " Result",
+ MvcUriComponentsBuilder.fromMethodName(
+ WorkflowController.class,
+ "serveFile",
+ workflowStatus.getUuid(),
+ "_" + serviceName.toLowerCase(),
+ migrationResult.getFileName().toString(),
+ null)
+ .build().toUri().toString());
+
+ // extract header information if result should be used in workflow
+ String useResult = resultResponse.getHeaders().getFirst("x-app4mc-use-result");
+ if (useResult == null || !useResult.toLowerCase().equals("false")) {
+ // the result should be used in the workflow
+ result = migrationResult;
+ }
+
+ if (resultResponse.getStatus() != HttpStatus.SC_OK) {
error = true;
}
@@ -577,10 +611,9 @@
}
if (baseUrl != null && toCheckUrl != null) {
- // TODO clarify: check only host, or host:port, or host:port/path
-// return baseUrl.getAuthority().equals(toCheckUrl.getAuthority());
-// return baseUrl.getAuthority().equals(toCheckUrl.getAuthority());
- return toCheckUrl.getAuthority().equals(baseUrl.getAuthority()) && toCheckUrl.getPath().startsWith(baseUrl.getPath());
+ // for now we only check that host:port are equal
+ return baseUrl.getAuthority().equals(toCheckUrl.getAuthority());
+// return toCheckUrl.getAuthority().equals(baseUrl.getAuthority()) && toCheckUrl.getPath().startsWith(baseUrl.getPath());
}
return false;
diff --git a/manager/src/main/resources/static/images/Logo_Panorama.png b/manager/src/main/resources/static/images/Logo_Panorama.png
new file mode 100644
index 0000000..7a9d68d
--- /dev/null
+++ b/manager/src/main/resources/static/images/Logo_Panorama.png
Binary files differ
diff --git a/manager/src/main/resources/static/images/app4mc-logo.png b/manager/src/main/resources/static/images/app4mc-logo.png
new file mode 100644
index 0000000..c9ab383
--- /dev/null
+++ b/manager/src/main/resources/static/images/app4mc-logo.png
Binary files differ
diff --git a/manager/src/main/resources/static/index.html b/manager/src/main/resources/static/index.html
index be3776e..8f9d694 100644
--- a/manager/src/main/resources/static/index.html
+++ b/manager/src/main/resources/static/index.html
@@ -1,12 +1,36 @@
<!DOCTYPE HTML>
-<html>
+<html xmlns:th="https://www.thymeleaf.org">
<head>
- <title>APP4MC Cloud Manager</title>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<title>APP4MC Cloud Manager</title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link href="webjars/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
+<script src="webjars/jquery/3.5.1/jquery.min.js"></script>
+<script type="module" src="webjars/popper.js/1.16.0/popper.min.js"></script>
+<script src="webjars/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</head>
<body>
- <h1>APP4MC Cloud Manager</h1>
- <p><a href="/workflow">Start Workflow</a></p>
- <p><a href="/admin">Administration</a></p>
+ <div class="container">
+ <div class="row justify-content-center">
+ <div class="col text-center" style="height:200px">
+ <img class="img-fluid" style="height:100%" src="/images/Logo_Panorama.png"/>
+ </div>
+ </div>
+ <div class="row align-items-end">
+ <div class="col" style="height:100px">
+ <img class="img-fluid float-right" style="height:100%; margin-right:10px;" src="/images/app4mc-logo.png"/>
+ </div>
+ <div class="col">
+ <div class="content-heading"><h1>Cloud Manager</h1></div>
+ </div>
+ </div>
+ <div class="row mt-3">
+ <div class="col">
+ <a class="btn btn-primary float-right" href="/workflow">Start Workflow</a>
+ </div>
+ <div class="col">
+ <a class="btn btn-secondary" href="/admin">Administration</a>
+ </div>
+ </div>
+ </div>
</body>
</html>
diff --git a/manager/src/main/resources/templates/admin.html b/manager/src/main/resources/templates/admin.html
index e5a077c..c2338c3 100644
--- a/manager/src/main/resources/templates/admin.html
+++ b/manager/src/main/resources/templates/admin.html
@@ -1,69 +1,111 @@
<html xmlns:th="https://www.thymeleaf.org">
<head>
- <title>APP4MC Cloud Manager - Administration</title>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<title>APP4MC Cloud Manager - Administration</title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link href="webjars/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
+<script src="webjars/jquery/3.5.1/jquery.min.js"></script>
+<script type="module" src="webjars/popper.js/1.16.0/popper.min.js"></script>
+<script src="webjars/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</head>
<body>
+ <div class="container">
+ <div class="row justify-content-center">
+ <div class="col text-center" style="height:200px">
+ <img class="img-fluid" style="height:100%" src="/images/Logo_Panorama.png"/>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col text-center">
+ <div class="content-heading"><h2>Configure available cloud services</h2></div>
+ </div>
+ </div>
- <div>
- <h2>Configure available APP4MC Cloud services</h2>
- </div>
- <div>
<form action="#" th:action="@{/admin/save}" th:object="${dto}" method="POST">
<fieldset>
- <table>
- <thead>
- <tr>
- <th>Service Name</th>
- <th>Service Base URL</th>
- <th>Service Description</th>
- </tr>
- </thead>
- <tbody>
- <tr th:each="service, stat : *{services}">
- <td><input th:field="*{services[__${stat.index}__].name}" /></td>
- <td><input th:field="*{services[__${stat.index}__].baseUrl}" /></td>
- <td><input th:field="*{services[__${stat.index}__].description}" /></td>
- </tr>
- </tbody>
- </table>
- <input type="submit" id="submitButton" th:value="Save">
- <input type="reset" id="resetButton" name="reset" th:value="Reset"/>
+
+ <div class="row mt-3">
+ <div class="col text-center">
+ <table class="table table-sm">
+ <thead>
+ <tr>
+ <th>Service Name</th>
+ <th>Service Base URL</th>
+ <th>Service Description</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr th:each="service, stat : *{services}">
+ <td><input th:field="*{services[__${stat.index}__].name}" class="form-control"/></td>
+ <td><input th:field="*{services[__${stat.index}__].baseUrl}" class="form-control"/></td>
+ <td><input th:field="*{services[__${stat.index}__].description}" class="form-control"/></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col text-right" style="padding-right:2px">
+ <input type="submit" id="submitServicesButton" th:value="Save" class="btn btn-primary"/>
+ </div>
+ <div class="col" style="padding-left:2px">
+ <input type="reset" id="resetServicesButton" name="reset" th:value="Reset" class="btn btn-secondary"/>
+ </div>
+ </div>
+
</fieldset>
</form>
- </div>
- <div>
- <h2>Configure Proxy</h2>
- </div>
- <div>
+
+ <div class="row mb-3 mt-5">
+ <div class="col text-center">
+ <div class="content-heading"><h2>Configure Proxy</h2></div>
+ </div>
+ </div>
+
<form action="/admin/saveProxy" method="POST">
<fieldset>
- <table>
- <tr>
- <td>Host:</td>
- <td><input type="text" name="proxy_host" th:value="${proxy_host}"/></td>
- </tr>
- <tr>
- <td>Port:</td>
- <td><input type="text" name="proxy_port" th:value="${proxy_port}"/></td>
- </tr>
- <tr>
- <td>User:</td>
- <td><input type="text" name="proxy_user" th:value="${proxy_user}"/></td>
- </tr>
- <tr>
- <td>Password:</td>
- <td><input type="password" name="proxy_pwd" th:value="${proxy_pwd}"/></td>
- </tr>
- </table>
- <input type="submit" id="submitButton" th:value="Save">
- <input type="reset" id="resetButton" name="reset" th:value="Reset"/>
+
+ <div class="form-row">
+ <div class="form-group col">
+ <label for="proxy_host">Host</label>
+ <input type="text" id="proxy_host" name="proxy_host" th:value="${proxy_host}" class="form-control"/>
+ </div>
+ </div>
+ <div class="form-row">
+ <div class="form-group col">
+ <label for="proxy_port">Port</label>
+ <input type="text" id="proxy_port" name="proxy_port" th:value="${proxy_port}" class="form-control"/>
+ </div>
+ </div>
+ <div class="form-row">
+ <div class="form-group col">
+ <label for="proxy_user">User</label>
+ <input type="text" id="proxy_user" name="proxy_user" th:value="${proxy_user}" class="form-control"/>
+ </div>
+ </div>
+ <div class="form-row">
+ <div class="form-group col">
+ <label for="proxy_pwd">Password</label>
+ <input type="password" id="proxy_pwd" name="proxy_pwd" th:value="${proxy_pwd}" class="form-control"/>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col text-right" style="padding-right:2px">
+ <input type="submit" id="submitProxyButton" th:value="Save" class="btn btn-primary mt-2"/>
+ </div>
+ <div class="col" style="padding-left:2px">
+ <input type="reset" id="resetProxyButton" name="reset" th:value="Reset" class="btn btn-secondary mt-2"/>
+ </div>
+ </div>
+
</fieldset>
</form>
- </div>
- <div>
- <a th:href="@{/}">Go back</a>
- </div>
+
+ <div class="row">
+ <div class="col">
+ <a th:href="@{/}" class="btn btn-dark">Go back</a>
+ </div>
+ </div>
+ </div>
</body>
</html>
\ No newline at end of file
diff --git a/manager/src/main/resources/templates/selectedServices.html b/manager/src/main/resources/templates/selectedServices.html
index 3709203..71058a5 100644
--- a/manager/src/main/resources/templates/selectedServices.html
+++ b/manager/src/main/resources/templates/selectedServices.html
@@ -6,12 +6,13 @@
<body>
<div th:fragment="servicesList" id="selectedServices">
- <span th:each="selected : ${workflowStatus.selectedServices}">
- <span th:text="${selected}">Service</span>
- <button type="button" class="btn btn-default" th:attr="onclick=|removeSelectedServices('${selected}')|" >remove</button>
- <br>
- </span>
- <select name="services" id="services" onchange="updateSelectedServices(this.value)">
+ <ul class="list-group">
+ <li th:each="selected : ${workflowStatus.selectedServices}" class="list-group-item d-flex justify-content-between">
+ <p class="p-0 m-0 flex-grow-1" th:text="${selected}">Service</p>
+ <a th:attr="onclick=|removeSelectedServices('${selected}')|" class="btn btn-secondary btn-sm">remove</a>
+ </li>
+ </ul>
+ <select name="services" id="services" onchange="updateSelectedServices(this.value)" class="form-control">
<option></option>
<option
th:each="service : ${cloudServiceDefinitions}"
diff --git a/manager/src/main/resources/templates/status.html b/manager/src/main/resources/templates/status.html
new file mode 100644
index 0000000..537c584
--- /dev/null
+++ b/manager/src/main/resources/templates/status.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html xmlns:th="https://www.thymeleaf.org">
+
+<head>
+</head>
+
+<body>
+ <div th:fragment="messagesList" id="workflowMessages">
+ <h4 th:if="not ${workflowStatus.messages.isEmpty()}">Processing log:</h4>
+ <ul>
+ <li th:text="${msg}" th:each="msg : ${workflowStatus.messages}">Message</li>
+ </ul>
+ </div>
+
+ <div th:fragment="errorList" id="workflowErrors">
+ <div th:if="not ${workflowStatus.errors.isEmpty()}">
+ <ul>
+ <li th:text="${msg}" th:each="msg : ${workflowStatus.errors}" style="color:red;font-weight:bold;">Message</li>
+ </ul>
+ </div>
+ </div>
+
+ <div th:fragment="resultList" id="workflowResults">
+ <div th:if="not ${workflowStatus.results.isEmpty()}">
+ <h4>Workflow results:</h4>
+ <ul class="list-group">
+ <li th:each="result : ${workflowStatus.results}" class="list-group-item d-flex justify-content-between">
+ <p class="p-0 m-0 flex-grow-1" th:text="${result.key}">Result</p>
+ <a class="btn btn-primary btn-sm mr-1" th:href="${result.value}">View</a>
+ <a class="btn btn-secondary btn-sm" th:href="${result.value + '?download=true'}">Download</a>
+ </li>
+ </ul>
+ <a th:href="@{/{uuid}/delete(uuid=${workflowStatus.uuid})}" class="btn btn-danger mt-4 mb-4">Delete</a>
+ </div>
+ </div>
+</body>
+</html>
diff --git a/manager/src/main/resources/templates/workflow.html b/manager/src/main/resources/templates/workflow.html
index 197888a..fdb7fef 100644
--- a/manager/src/main/resources/templates/workflow.html
+++ b/manager/src/main/resources/templates/workflow.html
@@ -2,7 +2,10 @@
<head>
<title>APP4MC Cloud Manager - Workflow</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
-<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
+<link href="webjars/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
+<script src="webjars/jquery/3.5.1/jquery.min.js"></script>
+<script type="module" src="webjars/popper.js/1.16.0/popper.min.js"></script>
+<script src="webjars/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script type="text/javascript">
function clearWorkflow() {
$.ajax({
@@ -10,7 +13,9 @@
url: '/clear',
success: function(result) {
$('#selectedServicesBlock').load('/selectedServices');
- // TODO refresh messages
+ $('#messagesBlock').load('/messages');
+ $('#errorsBlock').load('/errors');
+ $('#resultsBlock').load('/results');
}
});
}
@@ -37,23 +42,50 @@
$(document).ready(function(){
$('#selectedServicesBlock').load('/selectedServices');
+ $('#messagesBlock').load('/messages');
+ $('#errorsBlock').load('/errors');
+ $('#resultsBlock').load('/results');
});
</script>
</head>
<body>
+ <div class="container">
+ <div class="row justify-content-center">
+ <div class="col text-center" style="height:200px">
+ <img class="img-fluid" style="height:100%" src="/images/Logo_Panorama.png"/>
+ </div>
+ </div>
+ <div class="row mb-4">
+ <div class="col text-center">
+ <div class="content-heading"><h1>Cloud Service Workflow</h1></div>
+ </div>
+ </div>
- <div>
<form method="POST" enctype="multipart/form-data" action="/workflow">
- <table>
- <tr><td>Select input file to process:</td><td><input type="file" name="file" onchange="clearWorkflow()"/></td></tr>
- <tr>
- <td valign="top">Select service(s) to process:</td>
- <td>
- <div id="selectedServicesBlock">
+ <div class="form-row mb-3">
+ <div class="custom-file">
+ <input type="file" class="custom-file-input" id="customFile" name="file">
+ <label class="custom-file-label" for="customFile">Select input file to process</label>
+ <script>
+ $('#customFile').on('change',function(){
+ //get the file name
+ var fileName = $(this).val().split('\\').pop();
+ //replace the "Choose a file" label
+ $(this).next('.custom-file-label').html(fileName);
+ clearWorkflow();
+ })
+ </script>
+ </div>
+ </div>
+ <div class="form-row">
+ <div class="form-group col">
+ <label >Select service(s) to process</label>
+ <div id="selectedServicesBlock">
- </div>
- </td>
- </tr>
+ </div>
+ </div>
+ </div>
+ <table>
<tr th:if="${allProfiles != null and not allProfiles.isEmpty()}">
<td valign="top">Select validations to perform:</td>
<td>
@@ -65,36 +97,32 @@
</ul>
</td>
</tr>
- <tr><td></td><td><input type="submit" value="Start workflow" /></td></tr>
</table>
+ <div class="form-row">
+ <div class="col text-center">
+ <input type="submit" value="Start workflow" class="btn btn-primary mt-2"/>
+ </div>
+ </div>
</form>
+
+ <div id="messagesBlock">
+
</div>
- <div>
- <ul>
- <li th:text="${msg}" th:each="msg : ${workflowStatus.messages}">Message</li>
- </ul>
+ <div id="errorsBlock">
+
</div>
- <div th:if="not ${workflowStatus.errors.isEmpty()}">
- <ul>
- <li th:text="${msg}" th:each="msg : ${workflowStatus.errors}" style="color:red;font-weight:bold;">Message</li>
- </ul>
- </div>
+ <div id="resultsBlock">
- <div th:if="not ${workflowStatus.results.isEmpty()}">
- <span>Workflow results:</span>
- <ul>
- <li th:each="result : ${workflowStatus.results}">
- <a th:href="${result.value}" th:text="${result.key}">Result</a>
- </li>
- </ul>
- <p><a th:href="@{/{uuid}/delete(uuid=${workflowStatus.uuid})}">Delete</a></p>
</div>
+
+ <div class="row">
+ <div class="col">
+ <a th:href="@{/}" class="btn btn-dark">Go back</a>
+ </div>
+ </div>
- <div>
- <a th:href="@{/}">Go back</a>
</div>
-
</body>
</html>
\ No newline at end of file