Display more info and accept / display messages.
This commit is contained in:
parent
b28ccc13ea
commit
6b6e4173cb
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -14,3 +14,4 @@ bin/
|
|||
.settings
|
||||
.classpath
|
||||
.factorypath
|
||||
.vscode
|
||||
|
|
30
README.md
30
README.md
|
@ -2,21 +2,39 @@
|
|||
|
||||
Test micronaut web application.
|
||||
|
||||
## Container
|
||||
|
||||
## Usage
|
||||
|
||||
### Build application container
|
||||
### Build container
|
||||
|
||||
```shell
|
||||
docker build -t hellomicronaut .
|
||||
```
|
||||
|
||||
### Run container
|
||||
|
||||
```shell
|
||||
docker run --name hello1 -p 8080:8080 hellomicronaut
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Test with browser
|
||||
Requires Java JDK version 11+.
|
||||
|
||||
Connect to the container on the specified port:
|
||||
### Build
|
||||
|
||||
```shell
|
||||
./gradlew build
|
||||
```
|
||||
|
||||
### Run
|
||||
|
||||
```shell
|
||||
./gradlew run -t
|
||||
```
|
||||
|
||||
|
||||
## Test with browser
|
||||
|
||||
Connect to the container or localhost on the specified port:
|
||||
|
||||
<http://localhost:8080/>
|
||||
|
||||
|
|
11
build.gradle
11
build.gradle
|
@ -1,6 +1,7 @@
|
|||
plugins {
|
||||
id("com.github.johnrengelman.shadow") version "7.1.2"
|
||||
id("io.micronaut.application") version "3.7.3"
|
||||
id("com.magnetichq.client-dependencies") version "2.0.0"
|
||||
}
|
||||
|
||||
version = "0.1"
|
||||
|
@ -17,7 +18,7 @@ dependencies {
|
|||
implementation("jakarta.annotation:jakarta.annotation-api")
|
||||
runtimeOnly("ch.qos.logback:logback-classic")
|
||||
implementation("io.micronaut:micronaut-validation")
|
||||
|
||||
implementation("io.micronaut.views:micronaut-views-thymeleaf")
|
||||
}
|
||||
|
||||
|
||||
|
@ -40,4 +41,10 @@ micronaut {
|
|||
}
|
||||
|
||||
|
||||
|
||||
clientDependencies {
|
||||
installDir = 'src/main/resources/assets/vendor'
|
||||
npm {
|
||||
'bulma'('0.9.4', into: 'bulma') { include 'css/bulma.*' }
|
||||
'htmx.org'('1.8.5', into: 'htmx') { include 'htmx.js', 'ext/preload.js' }
|
||||
}
|
||||
}
|
|
@ -1,28 +1,71 @@
|
|||
package hello;
|
||||
|
||||
import io.micronaut.http.MediaType;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.micronaut.core.annotation.Nullable;
|
||||
import io.micronaut.core.util.CollectionUtils;
|
||||
import io.micronaut.http.HttpResponse;
|
||||
import io.micronaut.http.annotation.Consumes;
|
||||
import io.micronaut.http.annotation.Controller;
|
||||
import io.micronaut.http.annotation.Get;
|
||||
import io.micronaut.http.annotation.Produces;
|
||||
import io.micronaut.http.annotation.Post;
|
||||
import io.micronaut.views.View;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
@Controller("/")
|
||||
public class HelloController {
|
||||
|
||||
@Get("/")
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
public HttpResponse<String> index() {
|
||||
private static final Logger log = LoggerFactory.getLogger(HelloController.class);
|
||||
|
||||
|
||||
private static long counter = 0;
|
||||
private static Deque<String> msgDeque = new ArrayDeque<String>();
|
||||
private static String hostname;
|
||||
|
||||
|
||||
@PostConstruct
|
||||
void initialize() {
|
||||
log.info("initialize()");
|
||||
hostname = System.getenv("HOSTNAME");
|
||||
|
||||
}
|
||||
|
||||
@Get("/")
|
||||
@View("index")
|
||||
public HttpResponse<?> index() {
|
||||
String osName = System.getProperty("os.name");
|
||||
String osVersion = System.getProperty("os.version");
|
||||
String osArch = System.getProperty("os.arch");
|
||||
|
||||
String javaName = System.getProperty("java.vm.name");
|
||||
String javaVersion = System.getProperty("java.vm.version");
|
||||
String helloMessage = String.format("%s v%s, running %s %s on %s.", javaName, javaVersion, osName, osVersion, osArch);
|
||||
|
||||
String helloMessage = String.format("Hello from %s v%s, running %s %s on %s.", javaName, javaVersion, osName, osVersion, osArch);
|
||||
return HttpResponse.ok(CollectionUtils.mapOf("hostname", hostname, "info", helloMessage));
|
||||
}
|
||||
|
||||
return HttpResponse.ok(helloMessage);
|
||||
|
||||
@Post("/ping")
|
||||
@View("pong")
|
||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
public HttpResponse<?> ping(@Nullable String message) {
|
||||
if(message != null && !message.isEmpty()) {
|
||||
log.info(message);
|
||||
counter++;
|
||||
msgDeque.push(message);
|
||||
//msgDeque.add(message);
|
||||
//messages.add(0, message);
|
||||
if(msgDeque.size() > 25) {
|
||||
log.debug("Message deque: {}", msgDeque.size());
|
||||
msgDeque.removeLast();
|
||||
}
|
||||
}
|
||||
return HttpResponse.ok(CollectionUtils.mapOf("hostname", hostname, "counter", counter, "messages", msgDeque));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,18 @@
|
|||
micronaut:
|
||||
application:
|
||||
name: hello
|
||||
router:
|
||||
static-resources:
|
||||
default:
|
||||
enabled: true
|
||||
mapping: /**/*.html
|
||||
paths:
|
||||
- classpath:public
|
||||
assets:
|
||||
enabled: true
|
||||
mapping: /assets/**
|
||||
paths:
|
||||
- classpath:assets
|
||||
netty:
|
||||
default:
|
||||
allocator:
|
||||
|
|
11851
src/main/resources/assets/vendor/bulma/css/bulma.css
vendored
Normal file
11851
src/main/resources/assets/vendor/bulma/css/bulma.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
1
src/main/resources/assets/vendor/bulma/css/bulma.css.map
vendored
Normal file
1
src/main/resources/assets/vendor/bulma/css/bulma.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/main/resources/assets/vendor/bulma/css/bulma.min.css
vendored
Normal file
1
src/main/resources/assets/vendor/bulma/css/bulma.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
144
src/main/resources/assets/vendor/htmx/ext/preload.js
vendored
Normal file
144
src/main/resources/assets/vendor/htmx/ext/preload.js
vendored
Normal file
|
@ -0,0 +1,144 @@
|
|||
// This adds the "preload" extension to htmx. By default, this will
|
||||
// preload the targets of any tags with `href` or `hx-get` attributes
|
||||
// if they also have a `preload` attribute as well. See documentation
|
||||
// for more details
|
||||
htmx.defineExtension("preload", {
|
||||
|
||||
onEvent: function(name, event) {
|
||||
|
||||
// Only take actions on "htmx:afterProcessNode"
|
||||
if (name !== "htmx:afterProcessNode") {
|
||||
return;
|
||||
}
|
||||
|
||||
// SOME HELPER FUNCTIONS WE'LL NEED ALONG THE WAY
|
||||
|
||||
// attr gets the closest non-empty value from the attribute.
|
||||
var attr = function(node, property) {
|
||||
if (node == undefined) {return undefined;}
|
||||
return node.getAttribute(property) || node.getAttribute("data-" + property) || attr(node.parentElement, property)
|
||||
}
|
||||
|
||||
// load handles the actual HTTP fetch, and uses htmx.ajax in cases where we're
|
||||
// preloading an htmx resource (this sends the same HTTP headers as a regular htmx request)
|
||||
var load = function(node) {
|
||||
|
||||
// Called after a successful AJAX request, to mark the
|
||||
// content as loaded (and prevent additional AJAX calls.)
|
||||
var done = function(html) {
|
||||
if (!node.preloadAlways) {
|
||||
node.preloadState = "DONE"
|
||||
}
|
||||
|
||||
if (attr(node, "preload-images") == "true") {
|
||||
document.createElement("div").innerHTML = html // create and populate a node to load linked resources, too.
|
||||
}
|
||||
}
|
||||
|
||||
return function() {
|
||||
|
||||
// If this value has already been loaded, then do not try again.
|
||||
if (node.preloadState !== "READY") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Special handling for HX-GET - use built-in htmx.ajax function
|
||||
// so that headers match other htmx requests, then set
|
||||
// node.preloadState = TRUE so that requests are not duplicated
|
||||
// in the future
|
||||
var hxGet = node.getAttribute("hx-get") || node.getAttribute("data-hx-get")
|
||||
if (hxGet) {
|
||||
htmx.ajax("GET", hxGet, {handler:function(elt, info) {
|
||||
done(info.xhr.responseText);
|
||||
}});
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, perform a standard xhr request, then set
|
||||
// node.preloadState = TRUE so that requests are not duplicated
|
||||
// in the future.
|
||||
if (node.getAttribute("href")) {
|
||||
var r = new XMLHttpRequest();
|
||||
r.open("GET", node.getAttribute("href"));
|
||||
r.onload = function() {done(r.responseText);};
|
||||
r.send();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This function processes a specific node and sets up event handlers.
|
||||
// We'll search for nodes and use it below.
|
||||
var init = function(node) {
|
||||
|
||||
// If this node DOES NOT include a "GET" transaction, then there's nothing to do here.
|
||||
if (node.getAttribute("href") + node.getAttribute("hx-get") + node.getAttribute("data-hx-get") == "") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Guarantee that we only initialize each node once.
|
||||
if (node.preloadState !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get event name from config.
|
||||
var on = attr(node, "preload") || "mousedown"
|
||||
const always = on.indexOf("always") !== -1
|
||||
if (always) {
|
||||
on = on.replace('always', '').trim()
|
||||
}
|
||||
|
||||
// FALL THROUGH to here means we need to add an EventListener
|
||||
|
||||
// Apply the listener to the node
|
||||
node.addEventListener(on, function(evt) {
|
||||
if (node.preloadState === "PAUSE") { // Only add one event listener
|
||||
node.preloadState = "READY"; // Requred for the `load` function to trigger
|
||||
|
||||
// Special handling for "mouseover" events. Wait 100ms before triggering load.
|
||||
if (on === "mouseover") {
|
||||
window.setTimeout(load(node), 100);
|
||||
} else {
|
||||
load(node)() // all other events trigger immediately.
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Special handling for certain built-in event handlers
|
||||
switch (on) {
|
||||
|
||||
case "mouseover":
|
||||
// Mirror `touchstart` events (fires immediately)
|
||||
node.addEventListener("touchstart", load(node));
|
||||
|
||||
// WHhen the mouse leaves, immediately disable the preload
|
||||
node.addEventListener("mouseout", function(evt) {
|
||||
if ((evt.target === node) && (node.preloadState === "READY")) {
|
||||
node.preloadState = "PAUSE";
|
||||
}
|
||||
})
|
||||
break;
|
||||
|
||||
case "mousedown":
|
||||
// Mirror `touchstart` events (fires immediately)
|
||||
node.addEventListener("touchstart", load(node));
|
||||
break;
|
||||
}
|
||||
|
||||
// Mark the node as ready to run.
|
||||
node.preloadState = "PAUSE";
|
||||
node.preloadAlways = always;
|
||||
htmx.trigger(node, "preload:init") // This event can be used to load content immediately.
|
||||
}
|
||||
|
||||
// Search for all child nodes that have a "preload" attribute
|
||||
event.target.querySelectorAll("[preload]").forEach(function(node) {
|
||||
|
||||
// Initialize the node with the "preload" attribute
|
||||
init(node)
|
||||
|
||||
// Initialize all child elements that are anchors or have `hx-get` (use with care)
|
||||
node.querySelectorAll("a,[hx-get],[data-hx-get]").forEach(init)
|
||||
})
|
||||
}
|
||||
})
|
3399
src/main/resources/assets/vendor/htmx/htmx.js
vendored
Normal file
3399
src/main/resources/assets/vendor/htmx/htmx.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
45
src/main/resources/views/defaultLayout.html
Normal file
45
src/main/resources/views/defaultLayout.html
Normal file
|
@ -0,0 +1,45 @@
|
|||
<!DOCTYPE html>
|
||||
<html th:fragment="layout (title, content)" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||
<meta content="true" name="intercoolerjs:use-actual-http-method" />
|
||||
<title th:replace="${title}">Hello Micronaut</title>
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico">
|
||||
<link href="/assets/vendor/bulma/css/bulma.min.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar is-light" role="navigation" aria-label="Navigation" id="header-nav">
|
||||
<div class="navbar-brand">
|
||||
<a href="/" class="navbar-item">
|
||||
<!-- <img src="./images/bulma-logo.png" alt=""> -->
|
||||
<!--<img src="/assets/images/app-logo.svg" alt="" width="32" height="32" />-->
|
||||
</a>
|
||||
</div>
|
||||
<div class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
<p class="navbar-item">Hello Micronaut</p>
|
||||
</div>
|
||||
<div class="navbar-end">
|
||||
<p class="navbar-item">
|
||||
<a href="https://git.data.coop/nellemann/hellomicronaut" target="_blank">git.data.coop/nellemann/hellomicronaut</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<!-- ./Navigation -->
|
||||
|
||||
<div th:replace="${content}">
|
||||
<p>Layout content</p>
|
||||
</div>
|
||||
|
||||
<script src="/assets/vendor/htmx/htmx.js" type="text/javascript"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
52
src/main/resources/views/index.html
Normal file
52
src/main/resources/views/index.html
Normal file
|
@ -0,0 +1,52 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" th:replace="~{defaultLayout :: layout(~{::title}, ~{::section})}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<head>
|
||||
<title>Hello Micronaut</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
|
||||
<article class="message">
|
||||
<div class="message-header">
|
||||
<p>Hello Micronaut from <span th:text="${hostname}"></span></span></p>
|
||||
<button class="delete" aria-label="delete"></button>
|
||||
</div>
|
||||
<div class="message-body" th:text="${info}">
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
|
||||
<form hx-post="/ping">
|
||||
<div class="field has-addons" hx-target="#pong">
|
||||
<div class="control">
|
||||
<input name="message" class="input" type="text" placeholder="Enter message">
|
||||
</div>
|
||||
<div class="control">
|
||||
<a class="button is-info" hx-post="/ping" hx-include="[msg='message']">
|
||||
Submit
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="columns mt-4">
|
||||
<div class="collumn" id="pong" hx-post="/ping" hx-trigger="load, queue:last, every 5s">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
15
src/main/resources/views/pong.html
Normal file
15
src/main/resources/views/pong.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
<span class="tag is-warning is-medium" th:text="${hostname}">
|
||||
Host
|
||||
</span>
|
||||
|
||||
<span class="tag is-danger is-medium" th:text="${counter}">
|
||||
Count
|
||||
</span>
|
||||
|
||||
<div th:each="message: ${messages}">
|
||||
<article class="message mt-3">
|
||||
<div class="message-body">
|
||||
<span th:text="${message}">text</span>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
Loading…
Reference in a new issue