Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/MakeNowJust/heredoc/v2 v2.0.1
github.com/Masterminds/semver/v3 v3.3.0
github.com/Masterminds/sprig/v3 v3.3.0
github.com/a-h/templ v0.3.977
github.com/aws/aws-sdk-go v1.55.5
github.com/bombsimon/logrusr/v4 v4.1.0
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be
Expand Down Expand Up @@ -81,7 +82,7 @@ require (
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
Expand Down Expand Up @@ -113,7 +114,7 @@ require (
github.com/lib/pq v1.10.9 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
Expand Down
11 changes: 7 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8
github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
github.com/a-h/templ v0.3.977 h1:kiKAPXTZE2Iaf8JbtM21r54A8bCNsncrfnokZZSrSDg=
github.com/a-h/templ v0.3.977/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
Expand Down Expand Up @@ -101,8 +103,8 @@ github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjT
github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4=
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI=
Expand Down Expand Up @@ -225,8 +227,8 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
Expand Down Expand Up @@ -431,6 +433,7 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
Expand Down
221 changes: 221 additions & 0 deletions pkg/dashboard/components.templ
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
package dashboard

import "fmt"

templ Layout(data *dashboardData) {
<!DOCTYPE html>
<html lang="en" class="h-100">
<head>
<meta charset="UTF-8"/>
<link rel="icon" type="image/svg+xml" href="/assets/favicon.png"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta http-equiv="refresh" content="10"/>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous"/>
<title>KubeOne Dashboard</title>
</head>
<body class="d-flex flex-column h-100">
<header class="navbar navbar-expand-lg sticky-top bd-navbar border-bottom py-3 bg-body px-4 shadow-sm">
<nav class="container-l bd-gutter d-flex flex-wrap flex-lg-nowrap align-items-center">
<img src="/assets/kubeone-logo.png" alt="logo" style="height: 30px;"/>
<h4 class="mx-3 my-0">Dashboard</h4>
</nav>
</header>
<main class="flex-shrink-0 p-4 bg-body-tertiary flex-grow-1">
<div class="container-l d-flex flex-column h-100 gap-4">
<div class="card shadow-sm">
<div class="card-body">
<h4 class="card-title pb-2">Control-Plane Nodes</h4>
@NodesTable(data.ControlPlaneNodes)
</div>
if len(data.WorkerNodes) > 0 {
<div class="card-body">
<h4 class="card-title pb-2">Worker Nodes</h4>
@NodesTable(data.WorkerNodes)
</div>
}
</div>
<div class="card shadow-sm">
<div class="card-body">
<h4 class="card-title pb-2">Machine Deployments</h4>
@MachineDeploymentsTable(data.MachineDeployments)
</div>
</div>
</div>
</main>
<footer class="footer mt-auto py-3 border-top">
<div class="container-l px-4">
<span class="text-body-secondary">Powered by Kubermatic</span>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<script>
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
</script>
</body>
</html>
}

templ StatusPill(status string) {
<span
role="button"
class={ fmt.Sprintf("text-bg-%s rounded-circle", pillClass(status)) }
style="width:20px; height: 20px"
data-bs-toggle="tooltip"
title={ pillTitle(status) }
></span>
}

templ StatusBadge(status string) {
<span class={ fmt.Sprintf("badge text-bg-%s rounded-pill", badgeClass(status)) }>{ status }</span>
}

templ NodesTable(nodes []node) {
<div class="row row-cols-1 row-cols-md-4 g-3">
for _, n := range nodes {
<div class="col">
<div class="card">
<div class="card-header d-flex align-items-center gap-2">
@StatusPill(n.Status)
{ n.Name }
</div>
<div class="card-body">
<div class="row row-cols-2 g-2">
<div class="col-4"><strong>Version</strong></div>
<div class="col">{ n.Version }</div>
<div class="col-4"><strong>Seen</strong></div>
<div class="col">{ n.LastHeartbeatTime.String() }</div>
if n.IsControlPlane {
<div class="col-4"><strong>etcd</strong></div>
<div class="col">
<div class="d-flex">
if n.EtcdOK {
@StatusBadge("Ready")
} else {
@StatusBadge("Not Ready")
}
</div>
</div>
<div class="col-4"><strong>apiserver</strong></div>
<div class="col">
<div class="d-flex">
if n.APIServerOK {
@StatusBadge("Ready")
} else {
@StatusBadge("Not Ready")
}
</div>
</div>
}
</div>
</div>
</div>
</div>
}
</div>
}

templ MachineDeploymentsTable(machineDeployments []machineDeployment) {
<div class="mt-2">
<table class="table">
<thead>
<tr>
<th scope="col">Status</th>
<th scope="col">Name</th>
<th scope="col">Namespace</th>
<th scope="col">Replicas</th>
<th scope="col">Kubelet</th>
<th scope="col">Age</th>
</tr>
</thead>
<tbody>
for _, md := range machineDeployments {
<tr class="table-light shadow-sm">
<td>
<div class="d-flex">
@StatusPill("Ready")
</div>
</td>
<td><strong>{ md.Name }</strong></td>
<td>{ md.Namespace }</td>
<td>{ fmt.Sprintf("%d / %d", md.AvailableReplicas, md.Replicas) }</td>
<td>{ md.Kubelet }</td>
<td>{ md.Age.String() }</td>
</tr>
<tr>
<td colspan="7" class="p-0">
<h5 class="p-2 m-0">Machines</h5>
@MachinesTable(md.Machines)
</td>
</tr>
}
</tbody>
</table>
</div>
}

templ MachinesTable(machines *[]machine) {
<table class="table">
<thead>
<tr>
<th scope="col">Status</th>
<th scope="col">Name</th>
<th scope="col">Namespace</th>
<th scope="col">Node</th>
<th scope="col">Kubelet</th>
<th scope="col">Address</th>
<th scope="col">Age</th>
</tr>
</thead>
<tbody>
if machines != nil {
for _, m := range *machines {
<tr class={ templ.KV("table-danger", m.Deleted) }>
<td>
<div class="d-flex">
if !m.Deleted {
@StatusPill("Ready")
} else {
@StatusPill("Deleting")
}
</div>
</td>
<td>{ m.Namespace }</td>
<td>{ m.Name }</td>
<td>{ m.Node }</td>
<td>{ m.Kubelet }</td>
<td>{ m.Address }</td>
<td>{ m.Age.String() }</td>
</tr>
}
}
</tbody>
</table>
}

func pillClass(status string) string {
if status == "" {
return "secondary"
}
if status == "Ready" {
return "success"
}
return "danger"
}

func pillTitle(status string) string {
if status == "" {
return "Unknown"
}
return status
}

func badgeClass(status string) string {
if status == "" {
return "secondary"
}
if status == "Ready" {
return "success"
}
return "danger"
}
Loading