mirror of
https://github.com/Dvorinka/Dash.git
synced 2026-10-28 07:41:27 +00:00
docs+release: Phase 4 launch artifacts
README hero with real screenshots, install/config/widget docs, goreleaser for dash + dash-agent, GHCR release workflow, changelog, community files, agent systemd unit and Dockerfile, version stamping via -X main.version. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
97b8f5e87f
commit
a89a81d46c
23 files changed
+785
-13
No files matched your search
@@ -0,0 +1,89 @@
|
||||
# Widget development
|
||||
|
||||
A widget is two pieces:
|
||||
|
||||
1. a Go **fetcher** in `apps/backend/internal/widget/` that declares config
|
||||
fields and pulls live data, and
|
||||
2. a React **view** in `apps/frontend/src/widgets/` that renders the payload
|
||||
inside a tile.
|
||||
|
||||
Renderers own the tile chrome — widgets never touch layout, drag-drop, or
|
||||
popovers.
|
||||
|
||||
## Backend fetcher
|
||||
|
||||
One file, registered in `init()`:
|
||||
|
||||
```go
|
||||
package widget
|
||||
|
||||
type pihole struct{}
|
||||
|
||||
func init() { Register(pihole{}) }
|
||||
|
||||
func (pihole) Meta() Type {
|
||||
return Type{
|
||||
Type: "pihole",
|
||||
Name: "Pi-hole",
|
||||
Description: "Queries and block rate",
|
||||
Fields: []Field{
|
||||
{Key: "endpoint", Label: "Endpoint", Placeholder: "http://pi.hole", Required: true},
|
||||
{Key: "token", Label: "API token", Secret: true},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (pihole) Fetch(ctx context.Context, cfg json.RawMessage) (any, error) {
|
||||
// cfg holds the item's config JSON. Never log it — it may hold keys.
|
||||
}
|
||||
```
|
||||
|
||||
- `Meta()` drives the add-widget dialog: `GET /api/widgets/types` returns
|
||||
every registered type with its fields.
|
||||
- `Fetch` is called by `GET /api/widgets/:id/data`; responses are cached ~30s
|
||||
in-process. Return any JSON-marshalable value — it reaches the frontend as
|
||||
`data`.
|
||||
- Read config with the `cfgStr`/`cfgNum` helpers in the package.
|
||||
- Fetchers needing the database (monitor, domain, system) live in
|
||||
`internal/api` and register via `widget.Register` at router setup — the
|
||||
`widget` package stays a leaf with no DB dependency.
|
||||
|
||||
## Frontend view
|
||||
|
||||
One component in `apps/frontend/src/widgets/`, one line in the registry at
|
||||
`src/widgets/index.tsx`:
|
||||
|
||||
```tsx
|
||||
export function PiholeWidget({ data, error }: { item: Item; data?: WidgetData; error?: string }) {
|
||||
if (error) return <TileError msg={error} />;
|
||||
if (!data) return <TileLoading />;
|
||||
return <div>{data.blockedPercent}%</div>;
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
const registry: WidgetRegistry = {
|
||||
pihole: { view: PiholeWidget },
|
||||
};
|
||||
```
|
||||
|
||||
- `useWidgetData` polls `/api/widgets/:id/data` every 30s. `data` is the
|
||||
fetcher's payload, `error` is a human-readable upstream failure.
|
||||
- Payloads are free-form; read fields defensively (values may be absent on
|
||||
first paint).
|
||||
- `local: true` marks a widget as frontend-only (e.g. clock) — no fetcher, no
|
||||
polling.
|
||||
|
||||
## Config fields
|
||||
|
||||
`Fields` in `Meta()` render inputs in the add-widget dialog. `Secret: true`
|
||||
masks the input. Config is stored in `items.config` JSON, sent back to the
|
||||
fetcher on every poll, and never logged.
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] `apps/backend/internal/widget/<name>.go` — fetcher + `init()` + `Meta()`
|
||||
- [ ] `apps/frontend/src/widgets/<name>.tsx` — view component
|
||||
- [ ] registry entry in `src/widgets/index.tsx`
|
||||
- [ ] error and empty-data states render cleanly in the tile
|
||||
- [ ] upstream timeouts bounded (respect `ctx`, keep HTTP client timeouts)
|
||||
Reference in new issue
Block a user