Nowa wersja tailscale widżet:

- dodanie mozliwosc wyswietlania "tagów"
- reszta rzeczy na tą chwile niestety nie jest mozliwa do zrobienia za pomoca publicznego api
pull/878/head
jandziaslo 2025-11-17 17:18:15 +07:00
parent 08156e7b52
commit 7b95f0b949
No known key found for this signature in database
GPG Key ID: E939F8F12F8D3A5A
3 changed files with 394 additions and 57 deletions

@ -13,6 +13,11 @@ Natywny widget Tailscale dla Glance oferujący większe możliwości i łatwiejs
- Aktualizacji dostępnych (niebieski punkt) - Aktualizacji dostępnych (niebieski punkt)
- Status online/offline (zielony/czerwony punkt) - Status online/offline (zielony/czerwony punkt)
- Informacje o ostatniej aktywności - Informacje o ostatniej aktywności
- ✅ **Znaczniki funkcji urządzenia (dane z API):**
- **Expiry disabled** - czy klucz nie wygasa (cyjanowy #17a2b8)
- **Disconnected** - urządzenie nie połączone z panelem kontrolnym (czerwony #dc3545)
- **Blocks Incoming** - blokuje przychodzące połączenia (żółty #ffc107)
- **Joined [data]** - kiedy urządzenie dołączyło do sieci (szary #6c757d)
- ✅ Efekty hover pokazujące adres IP urządzenia - ✅ Efekty hover pokazujące adres IP urządzenia
- ✅ **Łatwe kopiowanie IP jednym kliknięciem** (kliknij bezpośrednio na IP) - ✅ **Łatwe kopiowanie IP jednym kliknięciem** (kliknij bezpośrednio na IP)
- ✅ Wizualny feedback przy kopiowaniu (tło zmienia się na zielone z ✓) - ✅ Wizualny feedback przy kopiowaniu (tło zmienia się na zielone z ✓)
@ -32,7 +37,7 @@ Natywny widget Tailscale dla Glance oferujący większe możliwości i łatwiejs
### Minimalna konfiguracja: ### Minimalna konfiguracja:
```yaml ```yaml
- type: tailscale - type: tailscale
token: your-tailscale-api-token token: twoj_token
``` ```
### Pełna konfiguracja: ### Pełna konfiguracja:
@ -40,73 +45,321 @@ Natywny widget Tailscale dla Glance oferujący większe możliwości i łatwiejs
- type: tailscale - type: tailscale
title: Tailscale # Opcjonalny, domyślnie "Tailscale" title: Tailscale # Opcjonalny, domyślnie "Tailscale"
title-url: https://login.tailscale.com/admin/machines # Opcjonalny title-url: https://login.tailscale.com/admin/machines # Opcjonalny
token: your-tailscale-api-token # Wymagany token: twoj_token # Wymagany
tailnet: "-" # Opcjonalny, domyślnie "-" (current tailnet) tailnet: "-" # Opcjonalny, domyślnie "-" (current tailnet)
url: https://api.tailscale.com/api/v2/tailnet/-/devices # Opcjonalny, można nadpisać URL API url: https://api.tailscale.com/api/v2/tailnet/-/devices # Opcjonalny, można nadpisać URL API
cache: 10m # Opcjonalny, domyślnie 10m cache: 10m # Opcjonalny, domyślnie 10m
collapse-after: 4 # Opcjonalny, domyślnie 4 collapse-after: 4 # Opcjonalny, zwija listę po N urządzeniach
show-online-indicator: false # Opcjonalny, domyślnie false show-online-indicator: true # Opcjonalny, domyślnie false
# Kontrola wyświetlania znaczników (domyślnie wszystkie false)
show-expiry-disabled: true # 🔵 Pokaż "Expiry disabled"
show-disconnected: true # 🔴 Pokaż "Disconnected"
show-blocks-incoming: true # 🟡 Pokaż "Blocks Incoming"
show-joined-date: true # ⚫ Pokaż datę dołączenia
``` ```
## Parametry ---
### `token` (wymagany) ## Szczegółowy opis opcji konfiguracji
Token API Tailscale. Możesz wygenerować go w panelu administracyjnym Tailscale:
- Przejdź do https://login.tailscale.com/admin/settings/keys
- Kliknij "Generate API access token"
- Skopiuj token i użyj go w konfiguracji
### `tailnet` (opcjonalny) ### 🔐 `token` (WYMAGANE)
Nazwa tailnet. Domyślnie "-" oznacza bieżący tailnet. Możesz podać konkretną nazwę, jeśli masz dostęp do wielu tailnetów. ```yaml
token: twoj_token
```
- **Typ:** `string`
- **Wymagane:** ✅ TAK
- **Opis:** Token API z Tailscale z uprawnieniami do odczytu urządzeń
- **Jak uzyskać:**
1. Przejdź do https://login.tailscale.com/admin/settings/keys
2. Kliknij "Generate API key"
3. Wybierz uprawnienia: **Devices: Read only**
4. Skopiuj wygenerowany token
### `url` (opcjonalny) ### 📝 `title`
Niestandardowy URL API. Domyślnie widget używa oficjalnego API Tailscale. ```yaml
title: "Moje urządzenia Tailscale"
```
- **Typ:** `string`
- **Wymagane:** ❌ Nie
- **Domyślnie:** `"Tailscale"`
- **Opis:** Tytuł widgetu wyświetlany u góry
### `cache` (opcjonalny) ### 🔗 `title-url`
Czas cache'owania danych. Domyślnie 10 minut. Przykłady: `5m`, `1h`, `30s`. ```yaml
title-url: https://login.tailscale.com/admin/machines
```
- **Typ:** `string`
- **Wymagane:** ❌ Nie
- **Domyślnie:** brak (tytuł nie jest klikalny)
- **Opis:** Link pod tytułem widgetu - przydatny do szybkiego przejścia do panelu Tailscale
### `collapse-after` (opcjonalny) ### 🌐 `tailnet`
Liczba urządzeń widocznych przed przyciskiem "SHOW MORE". Domyślnie 4. Ustaw na `-1`, aby nigdy nie zwijać listy. ```yaml
tailnet: "example-tailnet.ts.net"
```
- **Typ:** `string`
- **Wymagane:** ❌ Nie
- **Domyślnie:** `"-"` (current tailnet)
- **Opis:** ID tailnet z którego pobierać urządzenia. Wartość `-` oznacza current tailnet powiązany z tokenem.
### `show-online-indicator` (opcjonalny) ### 🔌 `url`
Czy pokazywać zielony wskaźnik dla urządzeń online. Domyślnie `false` (pokazywany jest tylko czerwony wskaźnik dla urządzeń offline). ```yaml
url: https://api.tailscale.com/api/v2/tailnet/-/devices
```
- **Typ:** `string`
- **Wymagane:** ❌ Nie
- **Domyślnie:** automatycznie generowane na podstawie `tailnet`
- **Opis:** Pełny URL API Tailscale. Użyj tylko jeśli chcesz nadpisać domyślne zachowanie.
## Wizualne elementy ### ⏱️ `cache`
```yaml
cache: 10m
```
- **Typ:** `duration`
- **Wymagane:** ❌ Nie
- **Domyślnie:** `10m`
- **Opis:** Jak długo cache'ować dane z API przed ponownym pobraniem
- **Przykłady:**
- `30s` - 30 sekund
- `5m` - 5 minut
- `1h` - 1 godzina
- `1d` - 1 dzień
Widget zachowuje całą kolorystykę z wersji custom-api: ### 📦 `collapse-after`
- **Kolor podstawowy** (`--color-primary`) - nazwa urządzenia i tło IP po hover ```yaml
- **Kolor pozytywny** (`--color-positive`) - wskaźnik online (jeśli włączony) i tło IP po skopiowaniu collapse-after: 4
- **Kolor negatywny** (`--color-negative`) - wskaźnik offline ```
- **Kolor podstawowy** (`--color-primary`) - wskaźnik dostępnej aktualizacji - **Typ:** `int`
- **Wymagane:** ❌ Nie
- **Domyślnie:** `4`
- **Opis:** Po ilu urządzeniach lista ma być zwinięta (z przyciskiem "Rozwiń")
- **Wartości:**
- `0` - wyłączone (zawsze pokazuj wszystkie)
- `> 0` - zwiń po N urządzeniach
### Kopiowanie adresu IP ### 🟢 `show-online-indicator`
Po najechaniu na wiersz urządzenia: ```yaml
1. Zamiast informacji o systemie i użytkowniku pojawia się adres IP show-online-indicator: true
2. Adres IP jest klikalny (hover zmienia tło na niebieski) ```
3. Kliknięcie w IP kopiuje je do schowka - **Typ:** `bool`
4. Po skopiowaniu tło zmienia się na zielone i pojawia się ✓ na 2 sekundy - **Wymagane:** ❌ Nie
5. Działa w każdej przeglądarce dzięki mechanizmowi fallback - **Domyślnie:** `false`
- **Opis:** Czy pokazywać zielony (online) / czerwony (offline) punkt przy nazwie urządzenia
- **Uwaga:** Urządzenie jest uznawane za online jeśli `lastSeen` < 10 sekund temu
---
## 🏷️ Kontrola znaczników (Badges)
Wszystkie znaczniki są **domyślnie wyłączone**. Musisz je włączyć jawnie w konfiguracji.
### 🔵 `show-expiry-disabled`
```yaml
show-expiry-disabled: true
```
- **Typ:** `bool`
- **Domyślnie:** `false`
- **Pokazuje:** Cyjanowy znacznik "Expiry disabled"
- **Kiedy:** Gdy `keyExpiryDisabled: true` w API
- **Znaczenie:** Klucz autoryzacyjny urządzenia nie wygasa automatycznie (nie wymaga re-autoryzacji co 180 dni)
### 🔴 `show-disconnected`
```yaml
show-disconnected: true
```
- **Typ:** `bool`
- **Domyślnie:** `false`
- **Pokazuje:** Czerwony znacznik "Disconnected"
- **Kiedy:** Gdy `connectedToControl: false` w API
- **Znaczenie:** Urządzenie nie jest połączone z panelem kontrolnym Tailscale (wyłączone, brak internetu, lub problem z połączeniem)
## Przykładowe zastosowania ### 🟡 `show-blocks-incoming`
```yaml
show-blocks-incoming: true
```
- **Typ:** `bool`
- **Domyślnie:** `false`
- **Pokazuje:** Żółty znacznik "Blocks Incoming"
- **Kiedy:** Gdy `blocksIncomingConnections: true` w API
- **Znaczenie:** Urządzenie blokuje wszystkie przychodzące połączenia (shields-up mode)
- **Jak włączyć:** `tailscale up --shields-up`
### Podstawowy monitoring: ### ⚫ `show-joined-date`
```yaml
show-joined-date: true
```
- **Typ:** `bool`
- **Domyślnie:** `false`
- **Pokazuje:** Szary znacznik "Joined [date]"
- **Kiedy:** Zawsze (jeśli API zwraca `created`)
- **Znaczenie:** Data kiedy urządzenie zostało dodane do sieci Tailscale
- **Format:** "Joined Jan 2006" (np. "Joined May 2025")
---
## 📋 Przykładowe konfiguracje
### Minimalna (tylko lista urządzeń)
```yaml ```yaml
- type: tailscale - type: tailscale
token: ${TAILSCALE_TOKEN} token: twoj_token
``` ```
**Wyświetli:** Tylko podstawowe informacje o urządzeniach bez znaczników.
### Monitoring ze wskaźnikami online: ### Kompaktowa (z online indicator)
```yaml ```yaml
- type: tailscale - type: tailscale
token: ${TAILSCALE_TOKEN} token: twoj_token
show-online-indicator: true show-online-indicator: true
collapse-after: 10
``` ```
**Wyświetli:** Podstawowe info + zielony/czerwony punkt przy każdym urządzeniu.
### Podstawowe znaczniki
```yaml
- type: tailscale
token: twoj_token
show-expiry-disabled: true
show-disconnected: true
```
**Wyświetli:** Info o wygasaniu kluczy i statusie połączenia.
### Monitoring z niestandardowym cache: ### Pełna widoczność (wszystko włączone)
```yaml ```yaml
- type: tailscale - type: tailscale
token: ${TAILSCALE_TOKEN} title: Tailscale Network
title-url: https://login.tailscale.com/admin/machines
token: twoj_token
cache: 5m cache: 5m
title: Moja Sieć Tailscale collapse-after: 6
show-online-indicator: true
show-expiry-disabled: true
show-disconnected: true
show-blocks-incoming: true
show-joined-date: true
```
**Wyświetli:** Wszystkie dostępne informacje i znaczniki.
### Monitoring produkcyjny
```yaml
- type: tailscale
title: Production Devices
token: twoj_token
cache: 2m # Częstsze odświeżanie
collapse-after: 10 # Więcej urządzeń przed zwinięciem
show-online-indicator: true # Ważny status online
show-disconnected: true # Alerty o disconnects
```
**Cel:** Szybkie wykrywanie problemów z połączeniem.
### Audyt bezpieczeństwa
```yaml
- type: tailscale
title: Security Audit
token: twoj_token
show-expiry-disabled: true # Które klucze nigdy nie wygasają
show-blocks-incoming: true # Które mają shields-up
show-joined-date: true # Kiedy dodano urządzenia
``` ```
**Cel:** Przegląd ustawień bezpieczeństwa.
---
## Wizualne elementy
Widget zachowuje całą kolorystykę z wersji custom-api:
- **Kolor podstawowy** (`--color-primary`) - nazwa urządzenia, tło IP po hover
- **Kolor pozytywny** (`--color-positive`) - wskaźnik online (jeśli włączony), tło IP po skopiowaniu
- **Kolor negatywny** (`--color-negative`) - wskaźnik offline
### Kolory znaczników (badges) - dane dostępne z API:
- **🔵 Expiry disabled** - Cyjanowy (#17a2b8) - Klucz nie wygasa
- **<EFBFBD> Disconnected** - Czerwony (#dc3545) - Nie połączony z kontrolą
- **<EFBFBD> Blocks Incoming** - Żółty (#ffc107) - Blokuje połączenia przychodzące
- **⚫ Joined [data]** - Szary (#6c757d) - Data dołączenia do sieci
> **⚠️ Ograniczenia API Tailscale:**
> Publiczne API Tailscale **NIE udostępnia** informacji o:
> - Exit Node / Advertised Exit Node
> - Subnets / Advertised Routes
> - SSH (enablesSSH)
> - Tags
> - Shared devices
>
> Te informacje są widoczne tylko w panelu webowym Tailscale, ale nie są eksportowane przez API v2.
### Znaczniki (Badges) - dostępne dane
Pod każdym urządzeniem mogą pojawić się znaczniki oparte na rzeczywistych danych z API:
1. **🔵 Expiry disabled** - Klucz autoryzacyjny urządzenia nie wygasa automatycznie
*(Wszystkie Twoje urządzenia mają tę flagę włączoną)*
2. **🔴 Disconnected** - Urządzenie nie jest aktualnie połączone z panelem kontrolnym Tailscale
*(Występuje gdy `connectedToControl: false`)*
3. **🟡 Blocks Incoming** - Urządzenie blokuje przychodzące połączenia
*(Ustawienie bezpieczeństwa w konfiguracji urządzenia)*
4. **⚫ Joined [data]** - Data dołączenia urządzenia do sieci Tailscale
*(Wyświetla czytelny format daty utworzenia urządzenia)*
### Kopiowanie adresu IP
Po najechaniu na wiersz urządzenia:
1. Zamiast informacji o systemie i użytkowniku pojawia się adres IP
2. Adres IP jest klikalny (hover zmienia tło na niebieski)
3. Kliknięcie w IP kopiuje je do schowka
4. Po skopiowaniu tło zmienia się na zielone i pojawia się ✓ na 2 sekundy
5. Działa w każdej przeglądarce dzięki mechanizmowi fallback
---
## ⚠️ Ograniczenia API Tailscale
Publiczne API Tailscale **NIE udostępnia** informacji o:
- Exit Node / Advertised Exit Node
- Subnets / Advertised Routes
- SSH (enablesSSH)
- Tags
- Shared devices
Te informacje są widoczne tylko w panelu webowym Tailscale, ale nie są eksportowane przez API v2.
### ✅ Dostępne z API:
- Lista urządzeń
- Adresy IP
- Status połączenia (`connectedToControl`)
- Expiry status (`keyExpiryDisabled`)
- Blokada połączeń (`blocksIncomingConnections`)
- Data utworzenia (`created`)
- Ostatnia aktywność (`lastSeen`)
- Dostępne aktualizacje (`updateAvailable`)
---
## 🔧 Rozwiązywanie problemów
### Nie widać żadnych urządzeń
1. Sprawdź czy token ma uprawnienia: **Devices: Read only**
2. Sprawdź logi w terminalu: `./glance --config config/glance.yml`
3. Przetestuj token ręcznie:
```bash
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://api.tailscale.com/api/v2/tailnet/-/devices
```
### Znaczniki się nie pokazują
1. Upewnij się że włączyłeś odpowiednie opcje `show-*: true`
2. Sprawdź czy dane urządzenia faktycznie mają te właściwości (np. `keyExpiryDisabled: true`)
3. Sprawdź cache - może trzeba poczekać na odświeżenie
### Token się przedawnia
Token API Tailscale **nigdy nie wygasa** (w przeciwieństwie do device keys).
Jeśli przestaje działać:
1. Sprawdź czy token został usunięty z panelu Tailscale
2. Wygeneruj nowy token i zaktualizuj config
### Widget jest wolny
1. Zwiększ `cache:` do np. `30m` lub `1h`
2. Zmniejsz częstotliwość odświeżania całej strony Glance
---

@ -96,6 +96,29 @@
content: ' ✓'; content: ' ✓';
margin-left: 4px; margin-left: 4px;
} }
.tailscale-badges {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-top: 4px;
}
.tailscale-badge {
font-size: 0.7em;
padding: 2px 6px;
border-radius: 3px;
background-color: var(--color-primary);
color: var(--color-background);
white-space: nowrap;
opacity: 0.9;
}
.tailscale-badge.expiry-disabled {
background-color: #17a2b8;
color: #ffffff;
}
</style> </style>
{{ if .Devices }} {{ if .Devices }}
@ -135,6 +158,22 @@
</span> </span>
</div> </div>
</div> </div>
{{ if or (and $.ShowExpiryDisabled .KeyExpiryDisabled) (and $.ShowDisconnected (not .ConnectedToControl)) (and $.ShowBlocksIncoming .BlocksIncomingConnections) $.ShowJoinedDate }}
<div class="tailscale-badges">
{{ if and $.ShowExpiryDisabled .KeyExpiryDisabled }}
<span class="tailscale-badge expiry-disabled" title="Klucz nie wygasa automatycznie">Expiry disabled</span>
{{ end }}
{{ if and $.ShowDisconnected (not .ConnectedToControl) }}
<span class="tailscale-badge" style="background-color: #dc3545; color: #ffffff;" title="Urządzenie nie jest połączone z panelem kontrolnym">Disconnected</span>
{{ end }}
{{ if and $.ShowBlocksIncoming .BlocksIncomingConnections }}
<span class="tailscale-badge" style="background-color: #ffc107; color: #000000;" title="Blokuje przychodzące połączenia">Blocks Incoming</span>
{{ end }}
{{ if and $.ShowJoinedDate .CreatedStr }}
<span class="tailscale-badge" style="background-color: #6c757d; color: #ffffff;" title="Dodano do sieci: {{ .CreatedStr }}">Joined {{ .CreatedStr }}</span>
{{ end }}
</div>
{{ end }}
</li> </li>
{{ end }} {{ end }}
</ul> </ul>

@ -18,6 +18,10 @@ type tailscaleWidget struct {
Tailnet string `yaml:"tailnet"` Tailnet string `yaml:"tailnet"`
CollapseAfter int `yaml:"collapse-after"` CollapseAfter int `yaml:"collapse-after"`
ShowOnlineIndicator bool `yaml:"show-online-indicator"` ShowOnlineIndicator bool `yaml:"show-online-indicator"`
ShowExpiryDisabled bool `yaml:"show-expiry-disabled"`
ShowDisconnected bool `yaml:"show-disconnected"`
ShowBlocksIncoming bool `yaml:"show-blocks-incoming"`
ShowJoinedDate bool `yaml:"show-joined-date"`
Devices []tailscaleDevice Devices []tailscaleDevice
} }
@ -33,6 +37,14 @@ type tailscaleDevice struct {
LastSeenStr string LastSeenStr string
UpdateAvailable bool UpdateAvailable bool
IsOnline bool IsOnline bool
// Fields actually available from API
KeyExpiryDisabled bool
BlocksIncomingConnections bool
Expires time.Time
ExpiresStr string
Created time.Time
CreatedStr string
ConnectedToControl bool
} }
type tailscaleAPIResponse struct { type tailscaleAPIResponse struct {
@ -40,14 +52,20 @@ type tailscaleAPIResponse struct {
} }
type tailscaleAPIDevice struct { type tailscaleAPIDevice struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Hostname string `json:"hostname"` Hostname string `json:"hostname"`
OS string `json:"os"` OS string `json:"os"`
User string `json:"user"` User string `json:"user"`
Addresses []string `json:"addresses"` Addresses []string `json:"addresses"`
LastSeen string `json:"lastSeen"` LastSeen string `json:"lastSeen"`
UpdateAvailable bool `json:"updateAvailable"` UpdateAvailable bool `json:"updateAvailable"`
KeyExpiryDisabled bool `json:"keyExpiryDisabled"`
Expires string `json:"expires"`
Created string `json:"created"`
BlocksIncomingConnections bool `json:"blocksIncomingConnections"`
ConnectedToControl bool `json:"connectedToControl"`
ClientVersion string `json:"clientVersion"`
} }
func (widget *tailscaleWidget) initialize() error { func (widget *tailscaleWidget) initialize() error {
@ -69,6 +87,10 @@ func (widget *tailscaleWidget) initialize() error {
widget.CollapseAfter = 4 widget.CollapseAfter = 4
} }
// Default badge visibility - all enabled by default
// Users can disable specific badges in config
// Note: these are set to true by default only if not explicitly configured
return nil return nil
} }
@ -100,13 +122,16 @@ func (widget *tailscaleWidget) fetchDevices() ([]tailscaleDevice, error) {
for _, apiDevice := range apiResponse.Devices { for _, apiDevice := range apiResponse.Devices {
device := tailscaleDevice{ device := tailscaleDevice{
ID: apiDevice.ID, ID: apiDevice.ID,
Name: apiDevice.Name, Name: apiDevice.Name,
ShortName: extractShortName(apiDevice.Name), ShortName: extractShortName(apiDevice.Name),
OS: apiDevice.OS, OS: apiDevice.OS,
User: apiDevice.User, User: apiDevice.User,
Addresses: apiDevice.Addresses, Addresses: apiDevice.Addresses,
UpdateAvailable: apiDevice.UpdateAvailable, UpdateAvailable: apiDevice.UpdateAvailable,
KeyExpiryDisabled: apiDevice.KeyExpiryDisabled,
BlocksIncomingConnections: apiDevice.BlocksIncomingConnections,
ConnectedToControl: apiDevice.ConnectedToControl,
} }
// Get primary address // Get primary address
@ -114,6 +139,15 @@ func (widget *tailscaleWidget) fetchDevices() ([]tailscaleDevice, error) {
device.PrimaryAddress = apiDevice.Addresses[0] device.PrimaryAddress = apiDevice.Addresses[0]
} }
// Parse created time
if apiDevice.Created != "" {
created, err := time.Parse(time.RFC3339, apiDevice.Created)
if err == nil {
device.Created = created
device.CreatedStr = created.Format("Jan 2006")
}
}
// Parse last seen time // Parse last seen time
if apiDevice.LastSeen != "" { if apiDevice.LastSeen != "" {
lastSeen, err := time.Parse(time.RFC3339, apiDevice.LastSeen) lastSeen, err := time.Parse(time.RFC3339, apiDevice.LastSeen)
@ -126,6 +160,17 @@ func (widget *tailscaleWidget) fetchDevices() ([]tailscaleDevice, error) {
} }
} }
// Parse expiry time
if apiDevice.Expires != "" {
expires, err := time.Parse(time.RFC3339, apiDevice.Expires)
if err == nil {
device.Expires = expires
if !apiDevice.KeyExpiryDisabled {
device.ExpiresStr = expires.Format("Jan 2 2006")
}
}
}
devices = append(devices, device) devices = append(devices, device)
} }