diff --git a/docs/configuration.md b/docs/configuration.md index 2ba1204..497da94 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1886,12 +1886,66 @@ If any of the child containers are down, their status will propagate up to the p | ---- | ---- | -------- | ------- | | hide-by-default | boolean | no | false | | sock-path | string | no | /var/run/docker.sock | +| category | string | no | | +| running-only | boolean | no | false | ##### `hide-by-default` Whether to hide the containers by default. If set to `true` you'll have to manually add a `glance.hide: false` label to each container you want to display. By default all containers will be shown and if you want to hide a specific container you can add a `glance.hide: true` label. ##### `sock-path` -The path to the Docker socket. +The path to the Docker socket. This can also be a [remote socket](https://docs.docker.com/engine/daemon/remote-access/) or proxied socket using something like [docker-socket-proxy](https://github.com/Tecnativa/docker-socket-proxy). + +###### `category` +Filter to only the containers which have this category specified via the `glance.category` label. Useful if you want to have multiple containers widgets, each showing a different set of containers. + +
+View example +
+ + +```yaml +services: + jellyfin: + image: jellyfin/jellyfin:latest + labels: + glance.name: Jellyfin + glance.icon: si:jellyfin + glance.url: https://jellyfin.domain.com + glance.category: media + + gitea: + image: gitea/gitea:latest + labels: + glance.name: Gitea + glance.icon: si:gitea + glance.url: https://gitea.domain.com + glance.category: dev-tools + + vaultwarden: + image: vaultwarden/server:latest + labels: + glance.name: Vaultwarden + glance.icon: si:vaultwarden + glance.url: https://vaultwarden.domain.com + glance.category: dev-tools +``` + +Then you can use the `category` property to filter the containers: + +```yaml +- type: docker-containers + title: Dev tool containers + category: dev-tools + +- type: docker-containers + title: Media containers + category: media +``` + +
+ +##### `running-only` +Whether to only show running containers. If set to `true` only containers that are currently running will be displayed. If set to `false` all containers will be displayed regardless of their state. #### Labels | Name | Description | @@ -1904,6 +1958,7 @@ The path to the Docker socket. | glance.hide | Whether to hide the container. If set to `true` the container will not be displayed. Defaults to `false`. | | glance.id | The custom ID of the container. Used to group containers under a single parent. | | glance.parent | The ID of the parent container. Used to group containers under a single parent. | +| glance.category | The category of the container. Used to filter containers by category. | ### DNS Stats Display statistics from a self-hosted ad-blocking DNS resolver such as AdGuard Home, Pi-hole, or Technitium. diff --git a/internal/glance/widget-docker-containers.go b/internal/glance/widget-docker-containers.go index f38cdeb..7676ca2 100644 --- a/internal/glance/widget-docker-containers.go +++ b/internal/glance/widget-docker-containers.go @@ -7,6 +7,7 @@ import ( "html/template" "net" "net/http" + "net/url" "sort" "strings" "time" @@ -17,6 +18,8 @@ var dockerContainersWidgetTemplate = mustParseTemplate("docker-containers.html", type dockerContainersWidget struct { widgetBase `yaml:",inline"` HideByDefault bool `yaml:"hide-by-default"` + RunningOnly bool `yaml:"running-only"` + Category string `yaml:"category"` SockPath string `yaml:"sock-path"` Containers dockerContainerList `yaml:"-"` } @@ -32,7 +35,7 @@ func (widget *dockerContainersWidget) initialize() error { } func (widget *dockerContainersWidget) update(ctx context.Context) { - containers, err := fetchDockerContainers(widget.SockPath, widget.HideByDefault) + containers, err := fetchDockerContainers(widget.SockPath, widget.HideByDefault, widget.Category, widget.RunningOnly) if !widget.canContinueUpdateAfterHandlingErr(err) { return } @@ -54,6 +57,7 @@ const ( dockerContainerLabelIcon = "glance.icon" dockerContainerLabelID = "glance.id" dockerContainerLabelParent = "glance.parent" + dockerContainerLabelCategory = "glance.category" ) const ( @@ -137,8 +141,8 @@ func dockerContainerStateToStateIcon(state string) string { } } -func fetchDockerContainers(socketPath string, hideByDefault bool) (dockerContainerList, error) { - containers, err := fetchAllDockerContainersFromSock(socketPath) +func fetchDockerContainers(socketPath string, hideByDefault bool, category string, runningOnly bool) (dockerContainerList, error) { + containers, err := fetchDockerContainersFromSource(socketPath, category, runningOnly) if err != nil { return nil, fmt.Errorf("fetching containers: %w", err) } @@ -239,17 +243,48 @@ func isDockerContainerHidden(container *dockerContainerJsonResponse, hideByDefau return hideByDefault } -func fetchAllDockerContainersFromSock(socketPath string) ([]dockerContainerJsonResponse, error) { - client := &http.Client{ - Timeout: 5 * time.Second, - Transport: &http.Transport{ - DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { - return net.Dial("unix", socketPath) +func fetchDockerContainersFromSource(source string, category string, runningOnly bool) ([]dockerContainerJsonResponse, error) { + var hostname string + + var client *http.Client + if strings.HasPrefix(source, "tcp://") || strings.HasPrefix(source, "http://") { + client = &http.Client{} + parsed, err := url.Parse(source) + if err != nil { + return nil, fmt.Errorf("parsing URL: %w", err) + } + + port := parsed.Port() + if port == "" { + port = "80" + } + + hostname = parsed.Hostname() + ":" + port + } else { + hostname = "docker" + client = &http.Client{ + Transport: &http.Transport{ + DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { + return net.Dial("unix", source) + }, }, - }, + } } - request, err := http.NewRequest("GET", "http://docker/containers/json?all=true", nil) + query := url.Values{} + query.Set("all", ternary(runningOnly, "false", "true")) + + if category != "" { + query.Set( + "filters", + fmt.Sprintf(`{"label": ["%s=%s"]}`, dockerContainerLabelCategory, category), + ) + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + request, err := http.NewRequestWithContext(ctx, "GET", "http://"+hostname+"/containers/json?"+query.Encode(), nil) if err != nil { return nil, fmt.Errorf("creating request: %w", err) }