mirror of
https://github.com/Dvorinka/Dash.git
synced 2026-10-28 07:41:27 +00:00
feat: phase 2 widgets — registry, fetchers, clock, config dialog
Backend: internal/widget registry (type metadata + Fetcher), Pi-hole v5 / AdGuard / Immich fetchers, /api/widgets/types + /api/widgets/:id/data with 30s TTL cache (failures cached too), kind+config on item create/patch. Config secrets are never logged or echoed in errors. Frontend: WidgetContent host maps config.type to a component, polls /data every 30s for server-backed types; clock renders client-side. Generic WidgetDialog builds its form from the registry's field list. Widget tiles are sortable items in both renderers. Generated with [Devin](https://devin.ai) Authored By: TDvorak <[email protected]> Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
6004105038
commit
8afc7df53b
21 files changed
+1072
-40
No files matched your search
@@ -9,10 +9,12 @@ import (
|
||||
)
|
||||
|
||||
type itemInput struct {
|
||||
SectionID string `json:"sectionId"`
|
||||
Name string `json:"name"`
|
||||
Icon string `json:"icon"`
|
||||
URLs []URLInput `json:"urls"`
|
||||
SectionID string `json:"sectionId"`
|
||||
Kind string `json:"kind"`
|
||||
Name string `json:"name"`
|
||||
Icon string `json:"icon"`
|
||||
Config *json.RawMessage `json:"config"`
|
||||
URLs []URLInput `json:"urls"`
|
||||
}
|
||||
|
||||
func (s *Server) createItem(c *gin.Context) {
|
||||
@@ -21,6 +23,14 @@ func (s *Server) createItem(c *gin.Context) {
|
||||
fail(c, http.StatusBadRequest, "sectionId and name required")
|
||||
return
|
||||
}
|
||||
kind := in.Kind
|
||||
if kind == "" {
|
||||
kind = "service"
|
||||
}
|
||||
if kind != "service" && kind != "widget" {
|
||||
fail(c, http.StatusBadRequest, "kind must be service or widget")
|
||||
return
|
||||
}
|
||||
for _, u := range in.URLs {
|
||||
if !validURL(u.URL) {
|
||||
fail(c, http.StatusBadRequest, "invalid url: "+u.URL)
|
||||
@@ -35,8 +45,8 @@ func (s *Server) createItem(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
it := Item{ID: newID("i"), SectionID: in.SectionID, Kind: "service", Name: in.Name, Icon: in.Icon,
|
||||
Position: midpoint(ptrOr(maxPos), nil), URLs: []URL{}}
|
||||
it := Item{ID: newID("i"), SectionID: in.SectionID, Kind: kind, Name: in.Name, Icon: in.Icon,
|
||||
Config: in.Config, Position: midpoint(ptrOr(maxPos), nil), URLs: []URL{}}
|
||||
|
||||
tx, err := s.db.Begin()
|
||||
if err != nil {
|
||||
@@ -45,8 +55,12 @@ func (s *Server) createItem(c *gin.Context) {
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
if _, err := tx.Exec(`INSERT INTO items (id, section_id, name, icon, position) VALUES (?, ?, ?, ?, ?)`,
|
||||
it.ID, it.SectionID, it.Name, it.Icon, it.Position); err != nil {
|
||||
var cfg any
|
||||
if in.Config != nil {
|
||||
cfg = string(*in.Config)
|
||||
}
|
||||
if _, err := tx.Exec(`INSERT INTO items (id, section_id, kind, name, icon, position, config) VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
it.ID, it.SectionID, it.Kind, it.Name, it.Icon, it.Position, cfg); err != nil {
|
||||
fail(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
@@ -76,9 +90,10 @@ func insertURLs(tx *sql.Tx, itemID string, in []URLInput, out *[]URL) error {
|
||||
|
||||
func (s *Server) updateItem(c *gin.Context) {
|
||||
var in struct {
|
||||
Name *string `json:"name"`
|
||||
Icon *string `json:"icon"`
|
||||
URLs *[]URLInput `json:"urls"`
|
||||
Name *string `json:"name"`
|
||||
Icon *string `json:"icon"`
|
||||
Config *json.RawMessage `json:"config"`
|
||||
URLs *[]URLInput `json:"urls"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&in); err != nil {
|
||||
fail(c, http.StatusBadRequest, "invalid body")
|
||||
@@ -113,6 +128,12 @@ func (s *Server) updateItem(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if in.Config != nil {
|
||||
if _, err := tx.Exec(`UPDATE items SET config = ? WHERE id = ?`, string(*in.Config), id); err != nil {
|
||||
fail(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
if in.URLs != nil {
|
||||
if _, err := tx.Exec(`DELETE FROM urls WHERE item_id = ?`, id); err != nil {
|
||||
fail(c, http.StatusInternalServerError, err.Error())
|
||||
|
||||
Reference in new issue
Block a user