mirror of https://github.com/glanceapp/glance.git
Nowosci i poprawki:
- Nowy widget do beszel wraz z dokumentacja - Zmiana wielkosci widgetu radyjkopull/878/head
parent
2e8eb157ee
commit
6009e0579e
@ -0,0 +1,76 @@
|
|||||||
|
# Widget Beszel - Instrukcja użycia
|
||||||
|
|
||||||
|
Widget Beszel pozwala na monitorowanie statusu serwerów w czasie rzeczywistym, wykorzystując lekkie i nowoczesne narzędzie monitoringu Beszel.
|
||||||
|
|
||||||
|
## Konfiguracja
|
||||||
|
|
||||||
|
Aby skonfigurować widget Beszel, dodaj następującą konfigurację do swojego pliku `glance.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- type: beszel
|
||||||
|
url: https://twoja-instancja-beszel.pl # URL do Twojej instancji Beszel (API)
|
||||||
|
redirect-url: https://twoja-instancja-beszel.pl # URL do interfejsu webowego Beszel
|
||||||
|
token: twoj-token-jwt # Token JWT (
|
||||||
|
cache: 10s # Częstotliwość odświeżania (domyślnie 10s)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Uzyskiwanie tokenu
|
||||||
|
|
||||||
|
Jeśli Twoja instancja Beszel jest zabezpieczona i nie udostępnia danych publicznie, będziesz potrzebować tokenu.
|
||||||
|
Obecnie widget obsługuje tokeny Bearer. Możesz uzyskać token logując się do Beszel i sprawdzając żądania sieciowe w przeglądarce lub generując go w panelu administracyjnym (jeśli dostępne).
|
||||||
|
|
||||||
|
## Funkcje widgetu
|
||||||
|
|
||||||
|
### 1. Monitorowanie statusu serwerów
|
||||||
|
|
||||||
|
Widget wyświetla listę serwerów z następującymi informacjami:
|
||||||
|
- **Nazwa serwera**: Nazwa zdefiniowana w Beszel.
|
||||||
|
- **Status**: Ikona serwera zmienia kolor w zależności od dostępności (zielony - online, czerwony - offline).
|
||||||
|
- **Uptime**: Czas nieprzerwanej pracy serwera (np. "5 days uptime").
|
||||||
|
|
||||||
|
### 2. Szczegółowe metryki
|
||||||
|
|
||||||
|
Dla każdego serwera wyświetlane są paski postępu z aktualnym użyciem zasobów:
|
||||||
|
- **CPU**: Aktualne użycie procesora w procentach.
|
||||||
|
- **RAM**: Aktualne użycie pamięci RAM w procentach.
|
||||||
|
- **DISK**: Zajętość głównego dysku w procentach.
|
||||||
|
|
||||||
|
### 3. Dodatkowe informacje (Popover)
|
||||||
|
|
||||||
|
Po najechaniu kursorem na ikonę serwera lub paski postępu, wyświetlane są dodatkowe informacje w dymku:
|
||||||
|
- **Host/IP**: Adres IP lub nazwa hosta serwera.
|
||||||
|
- **Kernel**: Wersja jądra systemu.
|
||||||
|
- **CPU Model**: Model procesora.
|
||||||
|
- **Load Average**: Średnie obciążenie systemu (1m, 5m, 15m) - dostępne po najechaniu na pasek CPU.
|
||||||
|
|
||||||
|
### 4. Linkowanie do systemu
|
||||||
|
|
||||||
|
Jeśli skonfigurowano parametr `redirect-url`, kliknięcie w nazwę serwera otworzy nową kartę z szczegółowymi statystykami tego konkretnego systemu w panelu Beszel.
|
||||||
|
|
||||||
|
## Przykładowa konfiguracja
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
pages:
|
||||||
|
- name: Home
|
||||||
|
columns:
|
||||||
|
- size: small
|
||||||
|
widgets:
|
||||||
|
- type: beszel
|
||||||
|
title: Serwery
|
||||||
|
url: https://beszel.example.com
|
||||||
|
redirect-url: https://beszel.example.com
|
||||||
|
token: TWóJ_TOKEN
|
||||||
|
cache: 5s
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rozwiązywanie problemów
|
||||||
|
|
||||||
|
### Widget nie wyświetla danych
|
||||||
|
|
||||||
|
1. Sprawdź czy URL do instancji Beszel jest poprawny i dostępny z serwera, na którym działa Glance.
|
||||||
|
2. Upewnij się, że endpoint `/api/collections/systems/records` jest dostępny.
|
||||||
|
3. Jeśli Twoja instancja wymaga autoryzacji, upewnij się, że podałeś poprawny token.
|
||||||
|
|
||||||
|
### Brakujące metryki
|
||||||
|
|
||||||
|
Niektóre metryki (np. Load Average) mogą nie być dostępne w zależności od wersji agenta Beszel zainstalowanego na monitorowanym serwerze.
|
||||||
@ -0,0 +1,88 @@
|
|||||||
|
{{ template "widget-base.html" . }}
|
||||||
|
|
||||||
|
{{- define "widget-content" }}
|
||||||
|
{{- $redirect := .RedirectURL }}
|
||||||
|
{{- range .Systems }}
|
||||||
|
<div class="server">
|
||||||
|
<div class="server-info">
|
||||||
|
<div class="server-details">
|
||||||
|
<div class="server-name size-h3">
|
||||||
|
{{ if ne $redirect "" }}
|
||||||
|
<a class="color-highlight" href="{{ $redirect }}/system/{{ .Name }}" target="_blank" rel="noopener">{{ .Name }}</a>
|
||||||
|
{{ else }}
|
||||||
|
<span class="color-highlight">{{ .Name }}</span>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ if eq .Status "up" }}
|
||||||
|
<span {{ dynamicRelativeTimeAttrs .BootTime }}></span> uptime
|
||||||
|
{{ else }}
|
||||||
|
<span class="color-negative">down</span>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="shrink-0" data-popover-type="html" data-popover-margin="0.2rem" data-popover-max-width="400px">
|
||||||
|
<div data-popover-html>
|
||||||
|
<div class="size-h5 text-compact">HOST</div>
|
||||||
|
<div class="color-highlight">{{ .Host }}</div>
|
||||||
|
|
||||||
|
<div class="size-h5 text-compact margin-top-3">KERNEL</div>
|
||||||
|
<div class="color-highlight">{{ .Info.Kernel }}</div>
|
||||||
|
|
||||||
|
<div class="size-h5 text-compact margin-top-3">CPU MODEL</div>
|
||||||
|
<div class="color-highlight">{{ .Info.CPUModel }}</div>
|
||||||
|
</div>
|
||||||
|
<svg class="server-icon" stroke="var(--color-{{ if eq .Status "up" }}positive{{ else }}negative{{ end }})" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M21.75 17.25v-.228a4.5 4.5 0 0 0-.12-1.03l-2.268-9.64a3.375 3.375 0 0 0-3.285-2.602H7.923a3.375 3.375 0 0 0-3.285 2.602l-2.268 9.64a4.5 4.5 0 0 0-.12 1.03v.228m19.5 0a3 3 0 0 1-3 3H5.25a3 3 0 0 1-3-3m19.5 0a3 3 0 0 0-3-3H5.25a3 3 0 0 0-3 3m16.5 0h.008v.008h-.008v-.008Zm-3 0h.008v.008h-.008v-.008Z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="server-stats">
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex items-end size-h5">
|
||||||
|
<div>CPU</div>
|
||||||
|
<div class="color-highlight margin-left-auto text-very-compact">{{ printf "%.1f" .Info.CPU }} <span class="color-base">%</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar" data-popover-type="html">
|
||||||
|
<div data-popover-html>
|
||||||
|
<div class="flex">
|
||||||
|
<div class="size-h5">1M AVG</div>
|
||||||
|
<div class="value-separator"></div>
|
||||||
|
<div class="color-highlight text-very-compact">{{ .Info.Load1 }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex margin-top-3">
|
||||||
|
<div class="size-h5">5M AVG</div>
|
||||||
|
<div class="value-separator"></div>
|
||||||
|
<div class="color-highlight text-very-compact">{{ .Info.Load5 }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex margin-top-3">
|
||||||
|
<div class="size-h5">15M AVG</div>
|
||||||
|
<div class="value-separator"></div>
|
||||||
|
<div class="color-highlight text-very-compact">{{ .Info.Load15 }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="progress-value{{ if ge .Info.CPU 85.0 }} progress-value-notice{{ end }}" style="--percent: {{ .Info.CPU }}"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex items-end size-h5">
|
||||||
|
<div>RAM</div>
|
||||||
|
<div class="color-highlight margin-left-auto text-very-compact">{{ printf "%.1f" .Info.Memory }} <span class="color-base">%</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-value{{ if ge .Info.Memory 85.0 }} progress-value-notice{{ end }}" style="--percent: {{ .Info.Memory }}"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex items-end size-h5">
|
||||||
|
<div>DISK</div>
|
||||||
|
<div class="color-highlight margin-left-auto text-very-compact">{{ printf "%.1f" .Info.Disk }} <span class="color-base">%</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-value{{ if ge .Info.Disk 85.0 }} progress-value-notice{{ end }}" style="--percent: {{ .Info.Disk }}"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
@ -0,0 +1,81 @@
|
|||||||
|
package glance
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var beszelWidgetTemplate = mustParseTemplate("beszel.html", "widget-base.html")
|
||||||
|
|
||||||
|
type beszelWidget struct {
|
||||||
|
widgetBase `yaml:",inline"`
|
||||||
|
URL string `yaml:"url"`
|
||||||
|
Token string `yaml:"token"`
|
||||||
|
RedirectURL string `yaml:"redirect-url"`
|
||||||
|
Systems []beszelSystem `yaml:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type beszelResponse struct {
|
||||||
|
Items []beszelSystem `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type beszelSystem struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Info beszelInfo `json:"info"`
|
||||||
|
BootTime time.Time `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type beszelInfo struct {
|
||||||
|
Kernel string `json:"k"`
|
||||||
|
Uptime float64 `json:"u"`
|
||||||
|
CPUModel string `json:"m"`
|
||||||
|
CPU float64 `json:"cpu"`
|
||||||
|
Memory float64 `json:"mp"`
|
||||||
|
Disk float64 `json:"dp"`
|
||||||
|
Load1 float64 `json:"l1"`
|
||||||
|
Load5 float64 `json:"l5"`
|
||||||
|
Load15 float64 `json:"l15"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *beszelWidget) initialize() error {
|
||||||
|
w.withTitle("Beszel").withCacheDuration(10 * time.Second)
|
||||||
|
if w.URL == "" {
|
||||||
|
return errors.New("beszel widget: url is required")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *beszelWidget) update(ctx context.Context) {
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "GET", w.URL+"/api/collections/systems/records", nil)
|
||||||
|
if err != nil {
|
||||||
|
w.withError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.Token != "" {
|
||||||
|
req.Header.Set("Authorization", "Bearer "+w.Token)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := decodeJsonFromRequest[*beszelResponse](defaultHTTPClient, req)
|
||||||
|
if err != nil {
|
||||||
|
w.withError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Systems = resp.Items
|
||||||
|
now := time.Now()
|
||||||
|
for i := range w.Systems {
|
||||||
|
w.Systems[i].BootTime = now.Add(-time.Duration(w.Systems[i].Info.Uptime) * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.withError(nil).scheduleNextUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *beszelWidget) Render() template.HTML {
|
||||||
|
return w.renderTemplate(w, beszelWidgetTemplate)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue