ClusterManager: Implements support of remote clusters.

After this change users will be able to:
* Create cluster and add/remove servers to it
* Install apps on remote cluster
* Move already installed apps between clusters
* Apps running on server being removed will auto-migrate
  to another server from that same cluster

This is achieved by:
* Installing and running minimal version of dodo on remote cluster
* Ingress-nginx is installed automatically on new clusters
* Next to nginx we run VPN client in the same pod, so that
  default cluster can establish secure communication with it
* Multiple reverse proxies are configured to get to the
  remote cluster service from ingress installed on default cluster.

Next steps:
* Support remote clusters in dodo apps (prototype ready)
* Clean up old cluster when moving app to the new one. Currently
  old cluster keeps running app pods even though no ingress can
  reach it anymore.

Change-Id: Iffc908c93416d4126a8e1c2832eae7b659cb8044
diff --git a/core/installer/welcome/appmanager-tmpl/app.html b/core/installer/welcome/appmanager-tmpl/app.html
index c6874cb..c1387ad 100644
--- a/core/installer/welcome/appmanager-tmpl/app.html
+++ b/core/installer/welcome/appmanager-tmpl/app.html
@@ -1,19 +1,7 @@
-{{ define "task" }}
-{{ range . }}
-<li aria-busy="{{ eq .Status 1 }}">
-	{{ if eq .Status 3 }}<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="black" d="M21 7L9 19l-5.5-5.5l1.41-1.41L9 16.17L19.59 5.59z"/></svg>{{ end }}{{ .Title }}{{ if .Err }} - {{ .Err.Error }} {{ end }}
-	{{ if .Subtasks }}
-	<ul>
-   		{{ template "task" .Subtasks }}
-	</ul>
-	{{ end }}
-</li>
-{{ end }}
-{{ end }}
-
 {{ define "schema-form" }}
   {{ $readonly := .ReadOnly }}
   {{ $networks := .AvailableNetworks }}
+  {{ $clusters := .AvailableClusters }}
   {{ $data := .Data }}
   {{ range $f := .Schema.Fields }}
   {{ $name := $f.Name }}
@@ -38,6 +26,25 @@
           {{ $schema.Name }}
 		  <input type="text" name="{{ $name }}" oninput="valueChanged({{ $name }}, this.value)" {{ if $readonly }}disabled{{ end }} value="{{ index $data $name }}" />
       </label>
+	{{ else if eq $schema.Kind 12 }}
+      <label {{ if $schema.Advanced }}hidden{{ end }}>
+          {{ $schema.Name }}
+		  <details class="dropdown">
+			  {{ $selectedCluster := index $data $name }}
+			  <summary id="{{ $name }}">{{ $selectedCluster }}</summary>
+			  <ul>
+				  {{ range $clusters }}
+					  {{ $selected := eq $selectedCluster .Name }}
+					  <li>
+						  <label>
+							  <input type="radio" name="{{ $name }}" oninput="clusterSelected('{{ $name }}', '{{ .Name }}', this.checked)" {{ if $selected }}checked{{ end }} />
+							  {{ .Name }}
+						  </label>
+					  </li>
+				  {{ end }}
+			  </ul>
+		  </details>
+      </label>
 	{{ else if eq $schema.Kind 3 }}
       <label {{ if $schema.Advanced }}hidden{{ end }}>
           {{ $schema.Name }}
@@ -49,8 +56,8 @@
 					  {{ $selected := eq $selectedNetwork .Name }}
 					  <li>
 						  <label>
-							  <input type="radio" name="{{ $name }}" oninput="networkSelected('{{ $name }}', '{{ .Name }}', this.checked)" {{ if $selected }}checked{{ end }} />
-							  {{ .Name }}
+							  <input type="radio" name="{{ $name }}" oninput="networkSelected('{{ $name }}', '{{ .Name }}', '{{ .Name }} - {{ .Domain }}', this.checked)" {{ if $selected }}checked{{ end }} />
+							  {{ .Name }} - {{ .Domain }}
 						  </label>
 					  </li>
 				  {{ end }}
@@ -75,7 +82,7 @@
 					  <li>
 						  <label>
 							  <input type="checkbox" name="{{ $networkName }}" oninput="multiNetworkSelected('{{ $name }}', '{{ $networkName }}', this.checked)" {{ if $selected }}checked{{ end }} />
-							  {{ .Name }}
+							  {{ .Name }} - {{ .Domain }}
 						  </label>
 					  </li>
 				  {{ end }}
@@ -132,25 +139,14 @@
 {{ define "content"}}
   {{ $schema := .App.Schema }}
   {{ $networks := .AvailableNetworks }}
+  {{ $clusters := .AvailableClusters }}
   {{ $instance := .Instance }}
-  {{ $renderForm := true }}
 
-  {{ if .Task }}
-    {{if or (eq .Task.Status 0) (eq .Task.Status 1) }}
-    {{ $renderForm = false }}
-    Installation in progress (feel free to navigate away from this page):
-    <ul class="progress">
-      {{ template "task" .Task.Subtasks }}
-    </ul>
-    {{ end }}
-  {{ end }}
-
-  {{ if $renderForm }}
   <form id="config-form">
 	  {{ if $instance }}
-		{{ template "schema-form" (dict "Schema" $schema "AvailableNetworks" $networks "ReadOnly" false "Data" ($instance.InputToValues $schema)) }}
+		{{ template "schema-form" (dict "Schema" $schema "AvailableNetworks" $networks "AvailableClusters" $clusters "ReadOnly" false "Data" ($instance.InputToValues $schema)) }}
 	  {{ else }}
-		{{ template "schema-form" (dict "Schema" $schema "AvailableNetworks" $networks "ReadOnly" false "Data" (dict)) }}
+		{{ template "schema-form" (dict "Schema" $schema "AvailableNetworks" $networks "AvailableClusters" $clusters "ReadOnly" false "Data" (dict)) }}
 	  {{ end }}
 	  {{ if $instance }}
 		<div class="grid">
@@ -161,7 +157,6 @@
 		<button type="submit" id="submit">{{ if $instance }}Update{{ else }}Install{{ end }}</button>
 	  {{ end }}
   </form>
-  {{ end }}
 
 <div id="toast-failure" class="toast hidden">
   <svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2S2 6.477 2 12s4.477 10 10 10Zm3-6L9 8m0 8l6-8"/></svg> {{ if $instance }}Update failed{{ else}}Install failed{{ end }}
@@ -200,11 +195,19 @@
 	 setValue(name, value, config);
  }
 
- function networkSelected(name, network, selected) {
+ function clusterSelected(name, cluster, selected) {
+	console.log(selected);
+	setValue(name, cluster, config);
+	let summary = document.getElementById(name);
+	summary.innerHTML = cluster;
+	summary.parentNode.removeAttribute("open");
+ }
+
+ function networkSelected(name, network, label, selected) {
 	console.log(selected);
 	setValue(name, network, config);
 	let summary = document.getElementById(name);
-	summary.innerHTML = network;
+	summary.innerHTML = label;
 	summary.parentNode.removeAttribute("open");
  }
 
@@ -310,29 +313,6 @@
 		 }
 	 });
  }
-
- {{ if .Task }}
- async function refresh() {
-	 try {
-		 const resp = await fetch(window.location.href);
-		 if (resp.ok) {
-			 var tmp = document.createElement("html");
-			 tmp.innerHTML = await resp.text();
-			 const progress = tmp.getElementsByClassName("progress")[0];
-			 if (progress) {
-				 document.getElementsByClassName("progress")[0].innerHTML = progress.innerHTML;
-			 } else {
-				 location.reload();
-			 }
-		 }
-	 } catch (error) {
-		 console.log(error);
-	 } finally {
-		 setTimeout(refresh, 3000);
-	 }
- }
- setTimeout(refresh, 3000);
- {{ end }}
 </script>
 
 {{end}}