From bb86f252ee3eb48c3e5f0064c93fa18c81c7c2dc Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Sun, 26 May 2024 11:08:21 +0300 Subject: [PATCH] show readme in Dev UI Signed-off-by: Phillip Kruger --- bom/dev-ui/pom.xml | 43 +++++++ .../deployment/menu/ReadmeProcessor.java | 61 ++++++++++ .../vertx-http/dev-ui-resources/pom.xml | 38 ++++++ .../main/resources/dev-ui/qwc/qwc-readme.js | 48 ++++++++ .../resources/dev-ui/state/devui-state.js | 2 +- .../runtime/readme/ReadmeJsonRPCService.java | 115 ++++++++++++++++++ 6 files changed, 306 insertions(+), 1 deletion(-) create mode 100644 extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/menu/ReadmeProcessor.java create mode 100644 extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-readme.js create mode 100644 extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/readme/ReadmeJsonRPCService.java diff --git a/bom/dev-ui/pom.xml b/bom/dev-ui/pom.xml index 8c304dd392210..6b83a3a190ab8 100644 --- a/bom/dev-ui/pom.xml +++ b/bom/dev-ui/pom.xml @@ -269,6 +269,49 @@ runtime + + + org.mvnpm + markdown-it + 14.1.0 + runtime + + + org.mvnpm + argparse + 2.0.1 + runtime + + + org.mvnpm + entities + 4.5.0 + runtime + + + org.mvnpm + linkify-it + 5.0.0 + runtime + + + org.mvnpm + mdurl + 2.0.0 + runtime + + + org.mvnpm + punycode.js + 2.3.1 + runtime + + + org.mvnpm + uc.micro + 2.1.0 + runtime + org.mvnpm.at.mvnpm diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/menu/ReadmeProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/menu/ReadmeProcessor.java new file mode 100644 index 0000000000000..12ec0a92de7bb --- /dev/null +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/menu/ReadmeProcessor.java @@ -0,0 +1,61 @@ +package io.quarkus.devui.deployment.menu; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; + +import io.quarkus.deployment.IsDevelopment; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.devui.deployment.InternalPageBuildItem; +import io.quarkus.devui.runtime.readme.ReadmeJsonRPCService; +import io.quarkus.devui.spi.JsonRPCProvidersBuildItem; +import io.quarkus.devui.spi.page.Page; + +/** + * This creates Readme Page + */ +public class ReadmeProcessor { + + private static final String NS = "devui-readme"; + + @BuildStep(onlyIf = IsDevelopment.class) + void createReadmePage(BuildProducer internalPageProducer) { + + String readme = getContents("README.md") + .orElse(getContents("readme.md") + .orElse(null)); + + if (readme != null) { + InternalPageBuildItem readmePage = new InternalPageBuildItem("Readme", 51); + + readmePage.addBuildTimeData("readme", readme); + + readmePage.addPage(Page.webComponentPageBuilder() + .namespace(NS) + .title("Readme") + .icon("font-awesome-brands:readme") + .componentLink("qwc-readme.js")); + + internalPageProducer.produce(readmePage); + } + } + + @BuildStep(onlyIf = IsDevelopment.class) + JsonRPCProvidersBuildItem createJsonRPCServiceForCache() { + return new JsonRPCProvidersBuildItem(NS, ReadmeJsonRPCService.class); + } + + private Optional getContents(String name) { + Path p = Path.of(name); + if (Files.exists(p)) { + try { + return Optional.of(Files.readString(p)); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + return Optional.empty(); + } +} diff --git a/extensions/vertx-http/dev-ui-resources/pom.xml b/extensions/vertx-http/dev-ui-resources/pom.xml index 3eb8c18a87fee..42a1cfb5ca862 100644 --- a/extensions/vertx-http/dev-ui-resources/pom.xml +++ b/extensions/vertx-http/dev-ui-resources/pom.xml @@ -112,6 +112,44 @@ runtime + + + + org.mvnpm + markdown-it + runtime + + + org.mvnpm + argparse + runtime + + + org.mvnpm + entities + runtime + + + org.mvnpm + linkify-it + runtime + + + org.mvnpm + mdurl + runtime + + + org.mvnpm + punycode.js + runtime + + + org.mvnpm + uc.micro + runtime + + org.mvnpm diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-readme.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-readme.js new file mode 100644 index 0000000000000..3966eb3eb919b --- /dev/null +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-readme.js @@ -0,0 +1,48 @@ +import { LitElement, html, css} from 'lit'; +import MarkdownIt from 'markdown-it'; +import { unsafeHTML } from 'lit/directives/unsafe-html.js'; +import { JsonRpc } from 'jsonrpc'; +import { readme } from 'devui-data'; + +/** + * This component shows the Readme page + */ +export class QwcReadme extends LitElement { + + jsonRpc = new JsonRpc("devui-readme", true); + + static styles = css` + .readme { + padding: 15px; + } + a { + color:var(--quarkus-blue); + } + `; + + static properties = { + _readme: {state:true}, + }; + + constructor() { + super(); + this.md = new MarkdownIt(); + this._readme = readme; + } + + connectedCallback() { + super.connectedCallback(); + this._observer = this.jsonRpc.streamReadme().onNext(jsonRpcResponse => { + this._readme = jsonRpcResponse.result; + }); + } + + render() { + if(this._readme){ + const htmlContent = this.md.render(this._readme); + return html`
${unsafeHTML(htmlContent)}
`; + } + } + +} +customElements.define('qwc-readme', QwcReadme); \ No newline at end of file diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/state/devui-state.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/state/devui-state.js index 20cf95a832bff..e6f1a9731fda5 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/state/devui-state.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/state/devui-state.js @@ -29,7 +29,7 @@ class DevUIState extends LitState { applicationInfo: applicationInfo, welcomeData: welcomeData, allConfiguration: allConfiguration, - ideInfo: ideInfo, + ideInfo: ideInfo, }; } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/readme/ReadmeJsonRPCService.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/readme/ReadmeJsonRPCService.java new file mode 100644 index 0000000000000..392ecfa90caa0 --- /dev/null +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/readme/ReadmeJsonRPCService.java @@ -0,0 +1,115 @@ +package io.quarkus.devui.runtime.readme; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.util.List; +import java.util.Optional; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import jakarta.enterprise.context.ApplicationScoped; + +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.infrastructure.Infrastructure; +import io.smallrye.mutiny.operators.multi.processors.BroadcastProcessor; +import io.smallrye.mutiny.subscription.Cancellable; + +@ApplicationScoped +public class ReadmeJsonRPCService { + private WatchService watchService = null; + private Cancellable cancellable; + private Path path = null; + private final BroadcastProcessor readmeStream = BroadcastProcessor.create(); + + @PostConstruct + public void init() { + this.path = getPath("README.md") + .orElse(getPath("readme.md") + .orElse(null)); + if (this.path != null) { + this.path = this.path.toAbsolutePath(); + Path parentDir = this.path.getParent(); + try { + watchService = FileSystems.getDefault().newWatchService(); + parentDir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, + StandardWatchEventKinds.ENTRY_MODIFY); + + this.cancellable = Multi.createFrom().emitter(emitter -> { + while (!Thread.currentThread().isInterrupted()) { + WatchKey key; + try { + key = watchService.take(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + List> events = key.pollEvents(); + for (WatchEvent event : events) { + WatchEvent.Kind kind = event.kind(); + Path changed = parentDir.resolve((Path) event.context()); + + if (changed.equals(this.path)) { + emitter.emit(event); + } + } + boolean valid = key.reset(); + if (!valid) { + emitter.complete(); + break; + } + } + }).runSubscriptionOn(Infrastructure.getDefaultExecutor()) + .onItem().transform(event -> { + readmeStream.onNext(getContent()); + return this.path; + }).subscribe().with((t) -> { + + }); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + @PreDestroy + public void cleanup() { + if (cancellable != null) { + cancellable.cancel(); + } + try { + if (watchService != null) { + watchService.close(); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private String getContent() { + try { + return Files.readString(this.path); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + public Multi streamReadme() { + return readmeStream; + } + + private Optional getPath(String name) { + Path p = Path.of(name); + if (Files.exists(p)) { + return Optional.of(p); + } + return Optional.empty(); + } + +}