Display more info and accept / display messages.
This commit is contained in:
parent
b28ccc13ea
commit
6b6e4173cb
|
@ -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,29 +1,72 @@
|
|||
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(helloMessage);
|
||||
return HttpResponse.ok(CollectionUtils.mapOf("hostname", hostname, "info", 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:
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -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)
|
||||
})
|
||||
}
|
||||
})
|
File diff suppressed because it is too large
Load Diff
|
@ -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>
|
|
@ -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>
|
|
@ -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 New Issue