diff --git a/internal/commands/root.go b/internal/commands/root.go
index 894afc6..f03d61c 100644
--- a/internal/commands/root.go
+++ b/internal/commands/root.go
@@ -16,6 +16,7 @@ package commands
import (
"context"
+ "fmt"
"log"
"os"
"os/signal"
@@ -76,6 +77,13 @@ func launch(cmd *cobra.Command, args []string) {
}
} else {
log.Printf("config %s", err)
+ webservice.Alerts = append(
+ webservice.Alerts,
+ webservice.Alert{
+ Level: "info",
+ Message: fmt.Sprintf("%s", err),
+ },
+ )
}
// start the electron app
diff --git a/internal/webservice/server.go b/internal/webservice/server.go
index 39a6e18..e6a7443 100755
--- a/internal/webservice/server.go
+++ b/internal/webservice/server.go
@@ -32,6 +32,14 @@ type wsRequest struct {
Data map[string]interface{} `json:"data"`
}
+// Alert basic structure to hold alert messages to pass to the UI
+type Alert struct {
+ Level string
+ Message string
+}
+
+var Alerts []Alert
+
// gorilla ws specific HTTP upgrade to WebSockets
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
@@ -70,6 +78,14 @@ func onOpen(w http.ResponseWriter, r *http.Request) {
ws = wsConn
log.Printf("WebSocket established with %s\n", ws.RemoteAddr().String())
+
+ // send any initialization alerts to UI and clear the queue
+ for len(Alerts) > 0 {
+ sendAlert(Alerts[0].Level, Alerts[0].Message)
+ Alerts[0] = Alert{}
+ Alerts = Alerts[1:]
+ }
+
go onMessage()
}
@@ -151,6 +167,20 @@ func handleAuth(w http.ResponseWriter, r *http.Request) {
}
}
+// SendAlert sends an alert message to the frontend handler
+// to display alerts in the UI itself
+func sendAlert(alertLvl string, msg string) {
+ if err := ws.WriteJSON(map[string]interface{}{
+ "type": "electron",
+ "component": "alert",
+ "level": alertLvl,
+ "message": msg,
+ "timestamp": time.Now().UnixNano() / 1000000,
+ }); err != nil {
+ onError(err)
+ }
+}
+
// WebServer will run the handler functions for WebSockets
// TODO: potentially add in the ability to serve static content
func WebServer() {
diff --git a/web/css/spinner.css b/web/css/spinner.css
new file mode 100644
index 0000000..a232e95
--- /dev/null
+++ b/web/css/spinner.css
@@ -0,0 +1,43 @@
+/* loading spinner CSS taken from SpinKit, http://tobiasahlin.com/spinkit/ */
+
+.spinner {
+ margin: 100px auto 0;
+ width: 70px;
+ text-align: center;
+}
+
+.spinner > div {
+ width: 18px;
+ height: 18px;
+ background-color: #333;
+
+ border-radius: 100%;
+ display: inline-block;
+ -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
+ animation: sk-bouncedelay 1.4s infinite ease-in-out both;
+}
+
+.spinner .bounce1 {
+ -webkit-animation-delay: -0.32s;
+ animation-delay: -0.32s;
+}
+
+.spinner .bounce2 {
+ -webkit-animation-delay: -0.16s;
+ animation-delay: -0.16s;
+}
+
+@-webkit-keyframes sk-bouncedelay {
+ 0%, 80%, 100% { -webkit-transform: scale(0) }
+ 40% { -webkit-transform: scale(1.0) }
+}
+
+@keyframes sk-bouncedelay {
+ 0%, 80%, 100% {
+ -webkit-transform: scale(0);
+ transform: scale(0);
+ } 40% {
+ -webkit-transform: scale(1.0);
+ transform: scale(1.0);
+ }
+}
\ No newline at end of file
diff --git a/web/images/Airship_Icon_2Color_RGB.svg b/web/images/Airship_Icon_2Color_RGB.svg
new file mode 100644
index 0000000..b77e172
--- /dev/null
+++ b/web/images/Airship_Icon_2Color_RGB.svg
@@ -0,0 +1,20 @@
+
+
+
diff --git a/web/images/Airship_Logo_Horizontal_2Color_RGB.svg b/web/images/Airship_Logo_Horizontal_2Color_RGB.svg
new file mode 100644
index 0000000..0065cf4
--- /dev/null
+++ b/web/images/Airship_Logo_Horizontal_2Color_RGB.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/web/images/avatar.jpg b/web/images/avatar.jpg
new file mode 100644
index 0000000..6f22565
Binary files /dev/null and b/web/images/avatar.jpg differ
diff --git a/web/index.html b/web/index.html
index 3d8b6fb..fddbad9 100755
--- a/web/index.html
+++ b/web/index.html
@@ -1,39 +1,111 @@
-
-
-
-
-
- Airship UI
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ Airship UI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/js/common.js b/web/js/common.js
index f980366..5c6e1a8 100755
--- a/web/js/common.js
+++ b/web/js/common.js
@@ -38,12 +38,14 @@ function addServiceDashboards(json) { // eslint-disable-line no-unused-vars
let namespace = cluster.namespaces[j];
for (let k = 0; k < namespace.dashboards.length; k++) {
let dash = namespace.dashboards[k];
- let { fqdn } = dash.fqdn;
- if (fqdn === undefined || fqdn === "") {
+ let fqdn = "";
+ if (dash.fqdn === undefined) {
fqdn = `${dash.hostname}.${cluster.namespaces[j].name}.${cluster.baseFqdn}`
+ } else {
+ ({ fqdn } = dash.fqdn);
}
let url = `${dash.protocol}://${fqdn}:${dash.port}/${dash.path}`;
- addDashboard("PluginDropdown", dash.name, url)
+ addDashboard("DashDropdown", dash.name, url)
}
}
}
@@ -53,7 +55,7 @@ function addServiceDashboards(json) { // eslint-disable-line no-unused-vars
// add them to the dropdown
function addPluginDashboards(json) { // eslint-disable-line no-unused-vars
for (let i = 0; i < json.length; i++) {
- if (json[i].executable.autoStart && json[i].dashboard !== undefined) {
+ if (json[i].executable.autoStart && json[i].dashboard.fqdn !== "") {
let dash = json[i].dashboard;
let url = `${dash.protocol}://${dash.fqdn}:${dash.port}/${dash.path}`;
addDashboard("PluginDropdown", json[i].name, url)
@@ -63,15 +65,21 @@ function addPluginDashboards(json) { // eslint-disable-line no-unused-vars
function addDashboard(navElement, name, url) {
let nav = document.getElementById(navElement);
+ let li = document.createElement("li");
+ li.className = "c-sidebar-nav-item";
let a = document.createElement("a");
+ a.className = "c-sidebar-nav-link";
+ let span = document.createElement("span");
+ span.className = "c-sidebar-nav-icon";
a.innerText = name;
a.onclick = () => {
let view = document.getElementById("DashView");
view.src = url;
- document.getElementById("MainDiv").style.display = "none";
document.getElementById("DashView").style.display = "";
}
- nav.appendChild(a);
+ a.appendChild(span);
+ li.appendChild(a);
+ nav.appendChild(li);
}
function authenticate(json) { // eslint-disable-line no-unused-vars
@@ -87,4 +95,41 @@ function removeElement(id) { // eslint-disable-line no-unused-vars
if (document.contains(document.getElementById(id))) {
document.getElementById(id).remove();
}
+}
+
+function showDismissableAlert(alertLevel, msg) { // eslint-disable-line no-unused-vars
+ let e = document.getElementById("alert-div");
+ let alertHeading = "";
+
+ switch (alertLevel) {
+ case "danger":
+ alertHeading = "Error";
+ break;
+ case "warning":
+ alertHeading = "Warning";
+ break;
+ default:
+ alertHeading = "Info";
+ }
+
+ let div = document.createElement("div");
+ div.className = `alert alert-${alertLevel} alert-dismissable fade show`;
+ div.setAttribute("role", "alert");
+ div.innerHTML = `${alertHeading}: ${msg}`;
+
+ // dismissable button
+ let btn = document.createElement("button");
+ btn.className = "close";
+ btn.type = "button";
+ btn.setAttribute("data-dismiss", "alert");
+ btn.setAttribute("aria-label", "Close");
+
+ let span = document.createElement("span");
+ span.setAttribute("aria-hidden", "true");
+ span.innerText = "×";
+
+ btn.appendChild(span);
+ div.appendChild(btn);
+
+ e.appendChild(div);
}
\ No newline at end of file
diff --git a/web/js/coreui-3.2.0/css/style.css b/web/js/coreui-3.2.0/css/style.css
index 88ebeb4..64d47bd 100644
--- a/web/js/coreui-3.2.0/css/style.css
+++ b/web/js/coreui-3.2.0/css/style.css
@@ -9802,7 +9802,7 @@ html:not([dir="rtl"]) .c-sidebar.c-sidebar-show.c-sidebar-right, html:not([dir="
}
.c-sidebar .c-sidebar-brand,.c-sidebar .c-sidebar-header {
- background: rgba(0, 0, 21, 0.2);
+ background: rgb(226, 226, 241);
}
.c-sidebar .c-sidebar-form .c-form-control {
@@ -11879,7 +11879,6 @@ html:not([dir="rtl"]) .list-inline {
-ms-flex-positive: 1;
flex-grow: 1;
min-width: 0;
- padding-top: 2rem;
}
@media (min-width: 768px) {
diff --git a/web/js/websocket.js b/web/js/websocket.js
index b456f25..c0eb6bc 100755
--- a/web/js/websocket.js
+++ b/web/js/websocket.js
@@ -68,11 +68,16 @@ function handleMessages(message) {
} else {
authComplete();
}
- addPluginDashboards(json["plugins"]);
- addServiceDashboards(json["dashboards"]);
- authenticate(json["authentication"]);
+ if (json["plugins"] !== null) {
+ addPluginDashboards(json["plugins"]);
+ }
+ if (json["dashboards"] !== null) {
+ addServiceDashboards(json["dashboards"]);
+ }
} else if (json["component"] === "authcomplete") {
authComplete();
+ } else if (json["component"] === "alert") {
+ showDismissableAlert(json["level"], json["message"]);
}
} else {
// TODO: determine if we're dispatching events or just doing function calls
@@ -115,10 +120,7 @@ function close(code) {
}
function authComplete() {
- document.getElementById("HeaderDiv").style.display = "";
document.getElementById("MainDiv").style.display = "";
- document.getElementById("DashView").style.display = "none";
- document.getElementById("FooterDiv").style.display = "";
}
function keepAlive() {