mirror of
https://github.com/mentos1386/zdravko.git
synced 2024-11-25 00:58:07 +00:00
feat(healthchecks): describe page
This commit is contained in:
parent
d84a8d3e21
commit
71ca6a9a73
6 changed files with 153 additions and 18 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue