feat(healthchecks): describe page

This commit is contained in:
Tine 2024-02-22 20:20:34 +01:00
parent d84a8d3e21
commit 71ca6a9a73
Signed by: mentos1386
SSH key fingerprint: SHA256:MNtTsLbihYaWF8j1fkOHfkKNlnN1JQfxEU/rBU8nCGw
6 changed files with 153 additions and 18 deletions

View file

@ -43,7 +43,7 @@ func getDailyHistory(history []models.HealthcheckHistory) *History {
numDays := 90
historyDailyMap := map[string]string{}
numOfSuccess := 0
numTotal := 1
numTotal := 0
for i := 0; i < numDays; i++ {
day := getDay(time.Now().AddDate(0, 0, -i).Truncate(time.Hour * 24))
@ -77,9 +77,14 @@ func getDailyHistory(history []models.HealthcheckHistory) *History {
historyDaily[i] = historyDailyMap[day]
}
uptime := 0
if numTotal > 0 {
uptime = 100 * numOfSuccess / numTotal
}
return &History{
History: historyDaily,
Uptime: 100 * numOfSuccess / numTotal,
Uptime: uptime,
}
}
@ -87,7 +92,7 @@ func getHourlyHistory(history []models.HealthcheckHistory) *History {
numHours := 48
historyHourlyMap := map[string]string{}
numOfSuccess := 0
numTotal := 1
numTotal := 0
for i := 0; i < numHours; i++ {
hour := getHour(time.Now().Add(time.Hour * time.Duration(-i)).Truncate(time.Hour))
@ -121,15 +126,20 @@ func getHourlyHistory(history []models.HealthcheckHistory) *History {
historyHourly[i] = historyHourlyMap[hour]
}
uptime := 0
if numTotal > 0 {
uptime = 100 * numOfSuccess / numTotal
}
return &History{
History: historyHourly,
Uptime: 100 * numOfSuccess / numTotal,
Uptime: uptime,
}
}
func (h *BaseHandler) Index(c echo.Context) error {
ctx := context.Background()
healthchecks, err := services.GetHealthchecksWithHistory(ctx, h.query)
healthchecks, err := services.GetHealthchecks(ctx, h.query)
if err != nil {
return err
}

View file

@ -21,10 +21,12 @@ func GetHealthcheck(ctx context.Context, q *query.Query, slug string) (*models.H
return q.Healthcheck.WithContext(ctx).Where(
q.Healthcheck.Slug.Eq(slug),
q.Healthcheck.DeletedAt.IsNull(),
).Preload(
q.Healthcheck.History,
).First()
}
func GetHealthchecksWithHistory(ctx context.Context, q *query.Query) ([]*models.Healthcheck, error) {
func GetHealthchecks(ctx context.Context, q *query.Query) ([]*models.Healthcheck, error) {
return q.Healthcheck.WithContext(ctx).Preload(
q.Healthcheck.History,
).Where(

View file

@ -716,6 +716,10 @@ video {
width: 100%;
}
.min-w-full {
min-width: 100%;
}
.max-w-screen-md {
max-width: 768px;
}
@ -826,6 +830,10 @@ video {
border-width: 1px;
}
.border-b-2 {
border-bottom-width: 2px;
}
.border-gray-300 {
--tw-border-opacity: 1;
border-color: rgb(209 213 219 / var(--tw-border-opacity));
@ -851,6 +859,11 @@ video {
background-color: rgb(249 250 251 / var(--tw-bg-opacity));
}
.bg-green-100 {
--tw-bg-opacity: 1;
background-color: rgb(220 252 231 / var(--tw-bg-opacity));
}
.bg-green-300 {
--tw-bg-opacity: 1;
background-color: rgb(134 239 172 / var(--tw-bg-opacity));
@ -861,6 +874,11 @@ video {
background-color: rgb(74 222 128 / var(--tw-bg-opacity));
}
.bg-red-100 {
--tw-bg-opacity: 1;
background-color: rgb(254 226 226 / var(--tw-bg-opacity));
}
.bg-red-300 {
--tw-bg-opacity: 1;
background-color: rgb(252 165 165 / var(--tw-bg-opacity));
@ -1004,6 +1022,10 @@ video {
text-transform: uppercase;
}
.leading-5 {
line-height: 1.25rem;
}
.leading-none {
line-height: 1;
}
@ -1012,6 +1034,10 @@ video {
letter-spacing: -0.025em;
}
.tracking-wider {
letter-spacing: 0.05em;
}
.text-blue-600 {
--tw-text-opacity: 1;
color: rgb(37 99 235 / var(--tw-text-opacity));
@ -1027,6 +1053,11 @@ video {
color: rgb(107 114 128 / var(--tw-text-opacity));
}
.text-gray-600 {
--tw-text-opacity: 1;
color: rgb(75 85 99 / var(--tw-text-opacity));
}
.text-gray-700 {
--tw-text-opacity: 1;
color: rgb(55 65 81 / var(--tw-text-opacity));
@ -1037,11 +1068,21 @@ video {
color: rgb(17 24 39 / var(--tw-text-opacity));
}
.text-green-800 {
--tw-text-opacity: 1;
color: rgb(22 101 52 / var(--tw-text-opacity));
}
.text-red-600 {
--tw-text-opacity: 1;
color: rgb(220 38 38 / var(--tw-text-opacity));
}
.text-red-800 {
--tw-text-opacity: 1;
color: rgb(153 27 27 / var(--tw-text-opacity));
}
.text-slate-500 {
--tw-text-opacity: 1;
color: rgb(100 116 139 / var(--tw-text-opacity));
@ -1358,6 +1399,10 @@ video {
}
@media (min-width: 768px) {
.md\:grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.md\:text-3xl {
font-size: 1.875rem;
line-height: 2.25rem;

View file

@ -39,7 +39,7 @@ export const options = {
thresholds: {
// http errors should be less than 1%
http_req_failed: ['rate<0.01'],
},
},
vus: 10,
duration: '5s',
};

View file

@ -1,14 +1,85 @@
{{define "settings"}}
<section class="relative overflow-x-auto shadow-md sm:rounded-lg p-5 text-gray-500 bg-white">
<h1 class="text-lg font-semibold text-gray-900">
{{ .Healthcheck.Name }}
</h1>
{{ .Healthcheck.ID }}
{{ .Healthcheck.Slug }}
{{ .Healthcheck.Schedule }}
{{ .Healthcheck.WorkerGroups }}
<pre class="block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500">
{{ .Healthcheck.Script }}
</pre>
<form action="/settings/healthchecks/{{ .Healthcheck.Slug }}" method="post">
<div class="grid md:grid-cols-2">
<h1 class="text-2xl font-semibold text-gray-900">
{{ .Healthcheck.Name }}
</h1>
<button type="submit" onclick="save()" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center inline-flex justify-self-end">Save</button>
</div>
<div class="mb-5">
<label for="workergroups" class="block mb-2 text-sm font-medium text-gray-900">Worker Groups</label>
<input type="text" name="workergroups" id="workergroups" value="{{ StringsJoin .Healthcheck.WorkerGroups " " }}" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"/>
</div>
<div class="mb-5">
<label for="schedule" class="block mb-2 text-sm font-medium text-gray-900">Schedule</label>
<input type="text" name="schedule" id="schedule" value="{{ .Healthcheck.Schedule }}" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"/>
</div>
<div class="mb-5">
<label for="script" class="block mb-2 text-sm font-medium text-gray-900">Script</label>
<input required type="hidden" id="script" name="script">
<div id="editor" class="block w-full h-96 rounded-lg border border-gray-300 overflow-hidden"></div>
</div>
</form>
<div>
<h2 class="text-lg font-semibold text-gray-900">History</h2>
<table class="min-w-full">
<thead>
<tr>
<th class="px-6 py-3 border-b-2 border-gray-300 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Status</th>
<th class="px-6 py-3 border-b-2 border-gray-300 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Created At</th>
<th class="px-6 py-3 border-b-2 border-gray-300 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Duration</th>
<th class="px-6 py-3 border-b-2 border-gray-300 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Note</th>
</tr>
</thead>
<tbody>
{{range .Healthcheck.History}}
<tr>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full {{if eq .Status "SUCCESS"}}bg-green-100 text-green-800{{else}}bg-red-100 text-red-800{{end}}">
{{ .Status }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
{{ .CreatedAt.Format "2006-01-02 15:04:05" }}
</td>
<td class="px-6 py-4 whitespace-nowrap">
{ .Duration }
</td>
<td class="px-6 py-4">
{{ .Note }}
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</section>
<script src="/static/monaco/vs/loader.js"></script>
<script>
function save() {
const script = window.editor.getValue();
document.getElementById('script').value = script;
}
script = `{{ .Healthcheck.Script }}`
require.config({ paths: { vs: '/static/monaco/vs' } });
require(['vs/editor/editor.main'], function () {
window.editor = monaco.editor.create(document.getElementById('editor'), {
value: script,
language: 'javascript',
minimap: { enabled: false },
codeLens: false,
contextmenu: false,
});
const divElem = document.getElementById('editor');
const resizeObserver = new ResizeObserver(entries => {
window.editor.layout();
});
resizeObserver.observe(divElem);
});
</script>
{{end}}

View file

@ -4,6 +4,7 @@ import (
"embed"
"io"
"log"
"strings"
"text/template"
"github.com/labstack/echo/v4"
@ -20,7 +21,13 @@ type Templates struct {
func load(files ...string) *template.Template {
files = append(files, base)
return template.Must(template.ParseFS(templates, files...))
t := template.New("default").Funcs(
template.FuncMap{
"StringsJoin": strings.Join,
})
return template.Must(t.ParseFS(templates, files...))
}
func loadSettings(files ...string) *template.Template {