diff --git a/dev-example/config.yaml b/dev-example/config.yaml
index 4a471a8..cf43692 100644
--- a/dev-example/config.yaml
+++ b/dev-example/config.yaml
@@ -1,6 +1,8 @@
# Used for running manager outside Kubernetes.
# Keep in sync with kustomize.yaml
"/Manager":
+ # If provided, is shown at top left before namespace
+ # clusterName: "test"
# The controller manages the VM
"/Controller":
namespace: vmop-dev
diff --git a/dev-example/kustomization.yaml b/dev-example/kustomization.yaml
index ae36fe1..70c6ae6 100644
--- a/dev-example/kustomization.yaml
+++ b/dev-example/kustomization.yaml
@@ -29,6 +29,7 @@ patches:
# Keep in sync with config.yaml
config.yaml: |
"/Manager":
+ # clusterName: "test"
"/Controller":
namespace: vmop-dev
"/Reconciler":
diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/console-brand.ftl.html b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/console-brand.ftl.html
index a81ee0a..9c9de88 100644
--- a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/console-brand.ftl.html
+++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/console-brand.ftl.html
@@ -1,2 +1,5 @@
${_("consoleTitle")}
\ No newline at end of file
+ src="${renderSupport.consoleResource('VM-Operator.svg')}"
+ >${_("consoleTitle")}
+ (<#if clusterName()??>${clusterName() + "/"}#if>${ namespace() })
\ No newline at end of file
diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java
index 3f7badb..d865ba4 100644
--- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java
+++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java
@@ -138,6 +138,8 @@ public class Controller extends Component {
.of("/var/run/secrets/kubernetes.io/serviceaccount/namespace");
if (Files.isReadable(path)) {
namespace = Files.lines(path).findFirst().orElse(null);
+ fire(new ConfigurationUpdate().add(componentPath(), "namespace",
+ namespace));
}
}
if (namespace == null) {
diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java
index d39718f..1175d8b 100644
--- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java
+++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java
@@ -18,6 +18,8 @@
package org.jdrupes.vmoperator.manager;
+import freemarker.template.TemplateMethodModelEx;
+import freemarker.template.TemplateModelException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -27,6 +29,9 @@ import java.net.URISyntaxException;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
@@ -54,6 +59,7 @@ import org.jgrapes.net.SocketServer;
import org.jgrapes.util.ComponentCollector;
import org.jgrapes.util.FileSystemWatcher;
import org.jgrapes.util.YamlConfigurationStore;
+import org.jgrapes.util.events.ConfigurationUpdate;
import org.jgrapes.util.events.WatchFile;
import org.jgrapes.webconlet.locallogin.LoginConlet;
import org.jgrapes.webconsole.base.BrowserLocalBackedKVStore;
@@ -69,9 +75,13 @@ import org.jgrapes.webconsole.vuejs.VueJsConsoleWeblet;
/**
* The application class.
*/
+@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.ExcessiveImports" })
public class Manager extends Component {
+ private static String version;
private static Manager app;
+ private String clusterName;
+ private String namespace = "unknown";
/**
* Instantiates a new manager.
@@ -79,26 +89,26 @@ public class Manager extends Component {
*
* @throws IOException Signals that an I/O exception has occurred.
*/
- @SuppressWarnings("PMD.TooFewBranchesForASwitchStatement")
+ @SuppressWarnings({ "PMD.TooFewBranchesForASwitchStatement",
+ "PMD.NcssCount" })
public Manager(CommandLine cmdLine) throws IOException {
+ super(new NamedChannel("manager"));
// Prepare component tree
attach(new NioDispatcher());
- Channel mgrChannel = new NamedChannel("manager");
- attach(new FileSystemWatcher(mgrChannel));
- attach(new Controller(mgrChannel));
+ attach(new FileSystemWatcher(channel()));
+ attach(new Controller(channel()));
// Configuration store with file in /etc/opt (default)
File cfgFile = new File(cmdLine.getOptionValue('c',
- "/etc/opt/" + VM_OP_NAME.replace("-", "") + "/config.yaml"))
- .getCanonicalFile();
+ "/etc/opt/" + VM_OP_NAME.replace("-", "") + "/config.yaml"));
logger.config(() -> "Using configuration from: " + cfgFile.getPath());
// Don't rely on night config to produce a good exception
// for this simple case
if (!Files.isReadable(cfgFile.toPath())) {
throw new IOException("Cannot read configuration file " + cfgFile);
}
- attach(new YamlConfigurationStore(mgrChannel, cfgFile, false));
- fire(new WatchFile(cfgFile.toPath()));
+ attach(new YamlConfigurationStore(channel(), cfgFile, false));
+ fire(new WatchFile(cfgFile.toPath()), channel());
// Prepare GUI
Channel httpTransport = new NamedChannel("guiTransport");
@@ -126,7 +136,12 @@ public class Manager extends Component {
return;
}
ConsoleWeblet consoleWeblet = guiHttpServer
- .attach(new VueJsConsoleWeblet(httpChannel, Channel.SELF, rootUri))
+ .attach(new VueJsConsoleWeblet(httpChannel, Channel.SELF, rootUri) {
+ @Override
+ protected Map createConsoleBaseModel() {
+ return augmentBaseModel(super.createConsoleBaseModel());
+ }
+ })
.prependClassTemplateLoader(getClass())
.prependResourceBundleProvider(getClass())
.prependConsoleResourceProvider(getClass());
@@ -154,6 +169,47 @@ public class Manager extends Component {
}));
}
+ private Map augmentBaseModel(Map base) {
+ base.put("version", version);
+ base.put("clusterName", new TemplateMethodModelEx() {
+ @Override
+ public Object exec(@SuppressWarnings("rawtypes") List arguments)
+ throws TemplateModelException {
+ return clusterName;
+ }
+ });
+ base.put("namespace", new TemplateMethodModelEx() {
+ @Override
+ public Object exec(@SuppressWarnings("rawtypes") List arguments)
+ throws TemplateModelException {
+ return namespace;
+ }
+ });
+ return base;
+ }
+
+ /**
+ * Configure the component.
+ *
+ * @param event the event
+ */
+ @Handler
+ @SuppressWarnings("PMD.DataflowAnomalyAnalysis")
+ public void onConfigurationUpdate(ConfigurationUpdate event) {
+ event.structured(componentPath()).ifPresent(c -> {
+ if (c.containsKey("clusterName")) {
+ clusterName = (String) c.get("clusterName");
+ } else {
+ clusterName = null;
+ }
+ });
+ event.structured(componentPath() + "/Controller").ifPresent(c -> {
+ if (c.containsKey("namespace")) {
+ namespace = (String) c.get("namespace");
+ }
+ });
+ }
+
/**
* Log the exception when a handling error is reported.
*
@@ -207,8 +263,10 @@ public class Manager extends Component {
try {
// Instance logger is not available yet.
var logger = Logger.getLogger(Manager.class.getName());
- logger.config(() -> "Version: "
- + Manager.class.getPackage().getImplementationVersion());
+ version = Optional.ofNullable(
+ Manager.class.getPackage().getImplementationVersion())
+ .orElse("unknown");
+ logger.config(() -> "Version: " + version);
logger.config(() -> "running on "
+ System.getProperty("java.vm.name")
+ " (" + System.getProperty("java.vm.version") + ")"
diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/package-info.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/package-info.java
index 48bc158..54d4efe 100644
--- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/package-info.java
+++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/package-info.java
@@ -140,6 +140,7 @@
* mgr .left. [FileSystemWatcher]
* mgr .right. [YamlConfigurationStore]
* mgr .. [Controller]
+ * mgr .up. [Manager]
* mgr .up. [VmWatcher]
* mgr .. [Reconciler]
*