mirror of
https://github.com/mentos1386/zdravko.git
synced 2024-11-21 23:33:34 +00:00
feat: sidebar design
This commit is contained in:
parent
b07297cf31
commit
cfc5668c48
10 changed files with 77 additions and 32 deletions
|
@ -20,7 +20,7 @@ trigger: |
|
||||||
}
|
}
|
||||||
|
|
||||||
# Example monitor code
|
# Example monitor code
|
||||||
monitor: |
|
check: |
|
||||||
import http from 'k6/http';
|
import http from 'k6/http';
|
||||||
|
|
||||||
export const options = {
|
export const options = {
|
||||||
|
|
|
@ -15,8 +15,8 @@ type IndexData struct {
|
||||||
*components.Base
|
*components.Base
|
||||||
Checks map[string]ChecksAndStatus
|
Checks map[string]ChecksAndStatus
|
||||||
ChecksLength int
|
ChecksLength int
|
||||||
TimeRange string
|
TimeRange string
|
||||||
Status models.CheckStatus
|
Status models.CheckStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
type Check struct {
|
type Check struct {
|
||||||
|
@ -33,11 +33,11 @@ type HistoryItem struct {
|
||||||
|
|
||||||
type History struct {
|
type History struct {
|
||||||
List []HistoryItem
|
List []HistoryItem
|
||||||
Uptime int
|
Uptime float64
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChecksAndStatus struct {
|
type ChecksAndStatus struct {
|
||||||
Status models.CheckStatus
|
Status models.CheckStatus
|
||||||
Checks []*Check
|
Checks []*Check
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,8 +47,8 @@ func getDateString(date time.Time) string {
|
||||||
|
|
||||||
func getHistory(history []*models.CheckHistory, period time.Duration, buckets int) *History {
|
func getHistory(history []*models.CheckHistory, period time.Duration, buckets int) *History {
|
||||||
historyMap := map[string]models.CheckStatus{}
|
historyMap := map[string]models.CheckStatus{}
|
||||||
numOfSuccess := 0
|
numOfSuccess := 0.0
|
||||||
numTotal := 0
|
numTotal := 0.0
|
||||||
|
|
||||||
for i := 0; i < buckets; i++ {
|
for i := 0; i < buckets; i++ {
|
||||||
dateString := getDateString(time.Now().Add(period * time.Duration(-i)).Truncate(period))
|
dateString := getDateString(time.Now().Add(period * time.Duration(-i)).Truncate(period))
|
||||||
|
@ -88,9 +88,9 @@ func getHistory(history []*models.CheckHistory, period time.Duration, buckets in
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uptime := 0
|
uptime := 0.0
|
||||||
if numTotal > 0 {
|
if numTotal > 0 {
|
||||||
uptime = 100 * numOfSuccess / numTotal
|
uptime = 100.0 * numOfSuccess / numTotal
|
||||||
}
|
}
|
||||||
|
|
||||||
return &History{
|
return &History{
|
||||||
|
@ -160,7 +160,7 @@ func (h *BaseHandler) Index(c echo.Context) error {
|
||||||
checksByGroup := map[string]ChecksAndStatus{}
|
checksByGroup := map[string]ChecksAndStatus{}
|
||||||
for _, check := range checksWithHistory {
|
for _, check := range checksWithHistory {
|
||||||
checksByGroup[check.Group] = ChecksAndStatus{
|
checksByGroup[check.Group] = ChecksAndStatus{
|
||||||
Status: statusByGroup[check.Group],
|
Status: statusByGroup[check.Group],
|
||||||
Checks: append(checksByGroup[check.Group].Checks, check),
|
Checks: append(checksByGroup[check.Group].Checks, check),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,7 +172,7 @@ func (h *BaseHandler) Index(c echo.Context) error {
|
||||||
NavbarActive: GetPageByTitle(Pages, "Status"),
|
NavbarActive: GetPageByTitle(Pages, "Status"),
|
||||||
Navbar: Pages,
|
Navbar: Pages,
|
||||||
},
|
},
|
||||||
Checks: checksByGroup,
|
Checks: checksByGroup,
|
||||||
TimeRange: timeRange,
|
TimeRange: timeRange,
|
||||||
Status: overallStatus,
|
Status: overallStatus,
|
||||||
})
|
})
|
||||||
|
|
|
@ -15,18 +15,37 @@ type SettingsSidebarGroup struct {
|
||||||
|
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
*components.Base
|
*components.Base
|
||||||
|
SettingsGroupName string
|
||||||
SettingsSidebarActive *components.Page
|
SettingsSidebarActive *components.Page
|
||||||
SettingsSidebar []SettingsSidebarGroup
|
SettingsSidebar []SettingsSidebarGroup
|
||||||
User *AuthenticatedUser
|
User *AuthenticatedUser
|
||||||
SettingsBreadcrumbs []*components.Page
|
SettingsBreadcrumbs []*components.Page
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findGroupForPage(groups []SettingsSidebarGroup, page *components.Page) *SettingsSidebarGroup {
|
||||||
|
for _, group := range groups {
|
||||||
|
for _, p := range group.Pages {
|
||||||
|
if p == page {
|
||||||
|
return &group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func NewSettings(user *AuthenticatedUser, page *components.Page, breadCrumbs []*components.Page) *Settings {
|
func NewSettings(user *AuthenticatedUser, page *components.Page, breadCrumbs []*components.Page) *Settings {
|
||||||
|
groupName := ""
|
||||||
|
group := findGroupForPage(SettingsSidebar, page)
|
||||||
|
if group != nil {
|
||||||
|
groupName = group.Group
|
||||||
|
}
|
||||||
|
|
||||||
return &Settings{
|
return &Settings{
|
||||||
Base: &components.Base{
|
Base: &components.Base{
|
||||||
NavbarActive: GetPageByTitle(Pages, "Settings"),
|
NavbarActive: GetPageByTitle(Pages, "Settings"),
|
||||||
Navbar: Pages,
|
Navbar: Pages,
|
||||||
},
|
},
|
||||||
|
SettingsGroupName: groupName,
|
||||||
SettingsSidebarActive: page,
|
SettingsSidebarActive: page,
|
||||||
SettingsSidebar: SettingsSidebar,
|
SettingsSidebar: SettingsSidebar,
|
||||||
SettingsBreadcrumbs: breadCrumbs,
|
SettingsBreadcrumbs: breadCrumbs,
|
||||||
|
@ -35,7 +54,7 @@ func NewSettings(user *AuthenticatedUser, page *components.Page, breadCrumbs []*
|
||||||
}
|
}
|
||||||
|
|
||||||
var SettingsPages = []*components.Page{
|
var SettingsPages = []*components.Page{
|
||||||
{Path: "/settings", Title: "Overview", Breadcrumb: "Overview"},
|
{Path: "/settings", Title: "Home", Breadcrumb: "Home"},
|
||||||
{Path: "/settings/incidents", Title: "Incidents", Breadcrumb: "Incidents"},
|
{Path: "/settings/incidents", Title: "Incidents", Breadcrumb: "Incidents"},
|
||||||
{Path: "/settings/targets", Title: "Targets", Breadcrumb: "Targets"},
|
{Path: "/settings/targets", Title: "Targets", Breadcrumb: "Targets"},
|
||||||
{Path: "/settings/targets/create", Title: "Targets Create", Breadcrumb: "Create"},
|
{Path: "/settings/targets/create", Title: "Targets Create", Breadcrumb: "Create"},
|
||||||
|
@ -57,7 +76,7 @@ var SettingsSidebar = []SettingsSidebarGroup{
|
||||||
{
|
{
|
||||||
Group: "Overview",
|
Group: "Overview",
|
||||||
Pages: []*components.Page{
|
Pages: []*components.Page{
|
||||||
GetPageByTitle(SettingsPages, "Overview"),
|
GetPageByTitle(SettingsPages, "Home"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -91,7 +110,7 @@ var SettingsSidebar = []SettingsSidebarGroup{
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingsOverview struct {
|
type SettingsHome struct {
|
||||||
*Settings
|
*Settings
|
||||||
WorkerGroupsCount int
|
WorkerGroupsCount int
|
||||||
ChecksCount int
|
ChecksCount int
|
||||||
|
@ -99,7 +118,7 @@ type SettingsOverview struct {
|
||||||
History []*services.CheckHistoryWithCheck
|
History []*services.CheckHistoryWithCheck
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *BaseHandler) SettingsOverviewGET(c echo.Context) error {
|
func (h *BaseHandler) SettingsHomeGET(c echo.Context) error {
|
||||||
cc := c.(AuthenticatedContext)
|
cc := c.(AuthenticatedContext)
|
||||||
ctx := c.Request().Context()
|
ctx := c.Request().Context()
|
||||||
|
|
||||||
|
@ -118,11 +137,11 @@ func (h *BaseHandler) SettingsOverviewGET(c echo.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Render(http.StatusOK, "settings_overview.tmpl", SettingsOverview{
|
return c.Render(http.StatusOK, "settings_home.tmpl", SettingsHome{
|
||||||
Settings: NewSettings(
|
Settings: NewSettings(
|
||||||
cc.Principal.User,
|
cc.Principal.User,
|
||||||
GetPageByTitle(SettingsPages, "Overview"),
|
GetPageByTitle(SettingsPages, "Home"),
|
||||||
[]*components.Page{GetPageByTitle(SettingsPages, "Overview")},
|
[]*components.Page{GetPageByTitle(SettingsPages, "Home")},
|
||||||
),
|
),
|
||||||
WorkerGroupsCount: workerGroups,
|
WorkerGroupsCount: workerGroups,
|
||||||
ChecksCount: checks,
|
ChecksCount: checks,
|
||||||
|
|
|
@ -52,7 +52,7 @@ func Routes(
|
||||||
// Settings
|
// Settings
|
||||||
settings := e.Group("/settings")
|
settings := e.Group("/settings")
|
||||||
settings.Use(h.Authenticated)
|
settings.Use(h.Authenticated)
|
||||||
settings.GET("", h.SettingsOverviewGET)
|
settings.GET("", h.SettingsHomeGET)
|
||||||
|
|
||||||
settings.GET("/triggers", h.SettingsTriggersGET)
|
settings.GET("/triggers", h.SettingsTriggersGET)
|
||||||
settings.GET("/triggers/:id", h.SettingsTriggersDescribeGET)
|
settings.GET("/triggers/:id", h.SettingsTriggersDescribeGET)
|
||||||
|
|
|
@ -64,11 +64,11 @@ code {
|
||||||
@apply bg-blue-700 text-white;
|
@apply bg-blue-700 text-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.monitors .time-range > a {
|
.checks .time-range > a {
|
||||||
@apply font-medium text-sm px-2.5 py-1 rounded-lg;
|
@apply font-medium text-sm px-2.5 py-1 rounded-lg;
|
||||||
@apply text-black bg-gray-100 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-400;
|
@apply text-black bg-gray-100 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-400;
|
||||||
}
|
}
|
||||||
.monitors .time-range > a.active {
|
.checks .time-range > a.active {
|
||||||
@apply bg-white hover:bg-gray-300 shadow;
|
@apply bg-white hover:bg-gray-300 shadow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1404,7 +1404,7 @@ code {
|
||||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.monitors .time-range > a {
|
.checks .time-range > a {
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
padding-left: 0.625rem;
|
padding-left: 0.625rem;
|
||||||
padding-right: 0.625rem;
|
padding-right: 0.625rem;
|
||||||
|
@ -1419,12 +1419,12 @@ code {
|
||||||
color: rgb(0 0 0 / var(--tw-text-opacity));
|
color: rgb(0 0 0 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.monitors .time-range > a:hover {
|
.checks .time-range > a:hover {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(209 213 219 / var(--tw-bg-opacity));
|
background-color: rgb(209 213 219 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.monitors .time-range > a:focus {
|
.checks .time-range > a:focus {
|
||||||
outline: 2px solid transparent;
|
outline: 2px solid transparent;
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
||||||
|
@ -1434,7 +1434,7 @@ code {
|
||||||
--tw-ring-color: rgb(156 163 175 / var(--tw-ring-opacity));
|
--tw-ring-color: rgb(156 163 175 / var(--tw-ring-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.monitors .time-range > a.active {
|
.checks .time-range > a.active {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||||
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||||
|
@ -1442,7 +1442,7 @@ code {
|
||||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.monitors .time-range > a.active:hover {
|
.checks .time-range > a.active:hover {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(209 213 219 / var(--tw-bg-opacity));
|
background-color: rgb(209 213 219 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
@ -1772,6 +1772,17 @@ code {
|
||||||
--tw-ring-color: rgb(252 165 165 / var(--tw-ring-opacity));
|
--tw-ring-color: rgb(252 165 165 / var(--tw-ring-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.has-\[\.active\]\:bg-white:has(.active) {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-\[\.active\]\:shadow-md:has(.active) {
|
||||||
|
--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||||
|
--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
|
||||||
|
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 640px) {
|
@media (min-width: 640px) {
|
||||||
.sm\:w-auto {
|
.sm\:w-auto {
|
||||||
width: auto;
|
width: auto;
|
||||||
|
|
|
@ -14,13 +14,15 @@
|
||||||
class="sidebar gap-2 flex flex-row flex-wrap justify-center lg:flex-col lg:w-48 h-fit text-sm font-medium text-gray-900"
|
class="sidebar gap-2 flex flex-row flex-wrap justify-center lg:flex-col lg:w-48 h-fit text-sm font-medium text-gray-900"
|
||||||
>
|
>
|
||||||
{{ range .SettingsSidebar }}
|
{{ range .SettingsSidebar }}
|
||||||
<li class="flex items-center gap-1 lg:flex-col lg:items-start">
|
<li
|
||||||
|
class="flex items-center gap-2 p-2 lg:flex-col lg:items-start rounded-lg has-[.active]:shadow-md has-[.active]:bg-white"
|
||||||
|
>
|
||||||
<p
|
<p
|
||||||
class="text-xs font-semibold text-gray-600 uppercase tracking-wider"
|
class="text-xs font-semibold text-gray-600 uppercase tracking-wider"
|
||||||
>
|
>
|
||||||
{{ .Group }}
|
{{ .Group }}
|
||||||
</p>
|
</p>
|
||||||
<ul class="flex flex-row flex-wrap gap-1 lg:flex-col">
|
<ul class="flex flex-row flex-wrap gap-1 lg:flex-col w-full">
|
||||||
{{ range .Pages }}
|
{{ range .Pages }}
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
|
@ -67,6 +69,19 @@
|
||||||
class="mx-8 lg:mx-0 grid justify-center lg:justify-start"
|
class="mx-8 lg:mx-0 grid justify-center lg:justify-start"
|
||||||
>
|
>
|
||||||
<ol class="inline-flex items-center">
|
<ol class="inline-flex items-center">
|
||||||
|
<li class="inline-flex items-center">
|
||||||
|
<p
|
||||||
|
class="inline-flex items-center text-sm font-medium text-gray-700"
|
||||||
|
>
|
||||||
|
{{ .SettingsGroupName }}
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="feather h-4 w-4 mx-1 text-gray-400 overflow-visible"
|
||||||
|
>
|
||||||
|
<use href="/static/icons/feather-sprite.svg#chevron-right" />
|
||||||
|
</svg>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
{{ range .SettingsBreadcrumbs }}
|
{{ range .SettingsBreadcrumbs }}
|
||||||
<li class="inline-flex items-center">
|
<li class="inline-flex items-center">
|
||||||
<a
|
<a
|
||||||
|
|
|
@ -12,8 +12,8 @@
|
||||||
<p
|
<p
|
||||||
class="mb-8 text-l font-normal text-gray-700 lg:text-l sm:px-8 lg:px-40"
|
class="mb-8 text-l font-normal text-gray-700 lg:text-l sm:px-8 lg:px-40"
|
||||||
>
|
>
|
||||||
Create a check to check your services and get notified when they
|
Create a check to check your services and get notified when they are
|
||||||
are down.
|
down.
|
||||||
</p>
|
</p>
|
||||||
<div class="flex flex-col gap-4 sm:flex-row sm:justify-center">
|
<div class="flex flex-col gap-4 sm:flex-row sm:justify-center">
|
||||||
<a
|
<a
|
||||||
|
@ -138,7 +138,7 @@
|
||||||
<h4>{{ .Name }}</h4>
|
<h4>{{ .Name }}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="justify-self-end text-sm">
|
<div class="justify-self-end text-sm">
|
||||||
{{ .History.Uptime }}% uptime
|
{{ printf "%.2f" .History.Uptime }}% uptime
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="grid gap-px col-span-2 grid-flow-col h-8 rounded overflow-hidden"
|
class="grid gap-px col-span-2 grid-flow-col h-8 rounded overflow-hidden"
|
||||||
|
|
|
@ -46,7 +46,7 @@ func NewTemplates() *Templates {
|
||||||
"404.tmpl": load("pages/404.tmpl"),
|
"404.tmpl": load("pages/404.tmpl"),
|
||||||
"index.tmpl": load("pages/index.tmpl"),
|
"index.tmpl": load("pages/index.tmpl"),
|
||||||
"incidents.tmpl": load("pages/incidents.tmpl"),
|
"incidents.tmpl": load("pages/incidents.tmpl"),
|
||||||
"settings_overview.tmpl": loadSettings("pages/settings_overview.tmpl"),
|
"settings_home.tmpl": loadSettings("pages/settings_home.tmpl"),
|
||||||
"settings_triggers.tmpl": loadSettings("pages/settings_triggers.tmpl"),
|
"settings_triggers.tmpl": loadSettings("pages/settings_triggers.tmpl"),
|
||||||
"settings_triggers_create.tmpl": loadSettings("pages/settings_triggers_create.tmpl"),
|
"settings_triggers_create.tmpl": loadSettings("pages/settings_triggers_create.tmpl"),
|
||||||
"settings_triggers_describe.tmpl": loadSettings("pages/settings_triggers_describe.tmpl"),
|
"settings_triggers_describe.tmpl": loadSettings("pages/settings_triggers_describe.tmpl"),
|
Loading…
Reference in a new issue