mirror of
https://github.com/amir20/dozzle.git
synced 2025-12-21 13:23:07 +01:00
feat: adds ability to support labels with | delimeter (#2276)
* feat: adds ability to support labels with | delimeter * fixes tests * updates docs
This commit is contained in:
2
.reflex
2
.reflex
@@ -1 +1 @@
|
|||||||
-r '\.(go)$' -R 'node_modules' -G '*_test.go' -s -- go run main.go --level debug --remote-host tcp://64.225.88.189:2376
|
-r '\.(go)$' -R 'node_modules' -G '*_test.go' -s -- go run main.go --level debug --remote-host tcp://64.225.88.189:2376|clashleaders.com
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<nav class="breadcrumb menu-label" aria-label="breadcrumbs">
|
<nav class="breadcrumb menu-label" aria-label="breadcrumbs">
|
||||||
<ul v-if="sessionHost">
|
<ul v-if="sessionHost">
|
||||||
<li>
|
<li>
|
||||||
<a href="#" @click.prevent="setHost(null)">{{ sessionHost }}</a>
|
<a href="#" @click.prevent="setHost(null)">{{ hosts[sessionHost].name }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="is-active">
|
<li class="is-active">
|
||||||
<a href="#" aria-current="page">{{ $t("label.containers") }}</a>
|
<a href="#" aria-current="page">{{ $t("label.containers") }}</a>
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
<transition :name="sessionHost ? 'slide-left' : 'slide-right'" mode="out-in">
|
<transition :name="sessionHost ? 'slide-left' : 'slide-right'" mode="out-in">
|
||||||
<ul class="menu-list" v-if="!sessionHost">
|
<ul class="menu-list" v-if="!sessionHost">
|
||||||
<li v-for="host in config.hosts">
|
<li v-for="host in config.hosts">
|
||||||
<a @click.prevent="setHost(host)">{{ host }}</a>
|
<a @click.prevent="setHost(host.host)">{{ host.name }}</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="menu-list" v-else>
|
<ul class="menu-list" v-else>
|
||||||
@@ -85,6 +85,13 @@ const sortedContainers = computed(() =>
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const hosts = computed(() =>
|
||||||
|
config.hosts.reduce((acc, item) => {
|
||||||
|
acc[item.host] = item;
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, { name: string; host: string }>)
|
||||||
|
);
|
||||||
|
|
||||||
const activeContainersById = computed(() =>
|
const activeContainersById = computed(() =>
|
||||||
activeContainers.value.reduce((acc, item) => {
|
activeContainers.value.reduce((acc, item) => {
|
||||||
acc[item.id] = item;
|
acc[item.id] = item;
|
||||||
@@ -101,7 +108,8 @@ const activeContainersById = computed(() =>
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
li.exited a, li.dead a {
|
li.exited a,
|
||||||
|
li.dead a {
|
||||||
color: #777;
|
color: #777;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,15 +26,15 @@
|
|||||||
<o-dropdown v-model="sessionHost" aria-role="list">
|
<o-dropdown v-model="sessionHost" aria-role="list">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<o-button variant="primary" type="button" size="small">
|
<o-button variant="primary" type="button" size="small">
|
||||||
<span>{{ sessionHost }}</span>
|
<span>{{ sessionHost ? hosts[sessionHost].name : "" }}</span>
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<carbon:caret-down />
|
<carbon:caret-down />
|
||||||
</span>
|
</span>
|
||||||
</o-button>
|
</o-button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<o-dropdown-item :value="value" aria-role="listitem" v-for="value in config.hosts" :key="value">
|
<o-dropdown-item :value="value.host" aria-role="listitem" v-for="value in config.hosts" :key="value">
|
||||||
<span>{{ value }}</span>
|
<span>{{ value.name }}</span>
|
||||||
</o-dropdown-item>
|
</o-dropdown-item>
|
||||||
</o-dropdown>
|
</o-dropdown>
|
||||||
</div>
|
</div>
|
||||||
@@ -111,6 +111,13 @@ const sortedContainers = computed(() =>
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const hosts = computed(() =>
|
||||||
|
config.hosts.reduce((acc, item) => {
|
||||||
|
acc[item.host] = item;
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, { name: string; host: string }>)
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
aside {
|
aside {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Container } from "@/models/Container";
|
|||||||
const sessionHost = useSessionStorage<string | null>("host", null);
|
const sessionHost = useSessionStorage<string | null>("host", null);
|
||||||
|
|
||||||
if (config.hosts.length === 1 && !sessionHost.value) {
|
if (config.hosts.length === 1 && !sessionHost.value) {
|
||||||
sessionHost.value = config.hosts[0];
|
sessionHost.value = config.hosts[0].host;
|
||||||
}
|
}
|
||||||
|
|
||||||
function persistentVisibleKeys(container: ComputedRef<Container>) {
|
function persistentVisibleKeys(container: ComputedRef<Container>) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ interface Config {
|
|||||||
secured: boolean;
|
secured: boolean;
|
||||||
maxLogs: number;
|
maxLogs: number;
|
||||||
hostname: string;
|
hostname: string;
|
||||||
hosts: string[];
|
hosts: { name: string; host: string }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const pageConfig = JSON.parse(text);
|
const pageConfig = JSON.parse(text);
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -25,7 +23,7 @@ import (
|
|||||||
type dockerClient struct {
|
type dockerClient struct {
|
||||||
cli dockerProxy
|
cli dockerProxy
|
||||||
filters filters.Args
|
filters filters.Args
|
||||||
host string
|
host *Host
|
||||||
}
|
}
|
||||||
|
|
||||||
type StdType int
|
type StdType int
|
||||||
@@ -69,7 +67,7 @@ type Client interface {
|
|||||||
ContainerLogsBetweenDates(context.Context, string, time.Time, time.Time, StdType) (io.ReadCloser, error)
|
ContainerLogsBetweenDates(context.Context, string, time.Time, time.Time, StdType) (io.ReadCloser, error)
|
||||||
ContainerStats(context.Context, string, chan<- ContainerStat) error
|
ContainerStats(context.Context, string, chan<- ContainerStat) error
|
||||||
Ping(context.Context) (types.Ping, error)
|
Ping(context.Context) (types.Ping, error)
|
||||||
Host() string
|
Host() *Host
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClientWithFilters creates a new instance of Client with docker filters
|
// NewClientWithFilters creates a new instance of Client with docker filters
|
||||||
@@ -89,10 +87,10 @@ func NewClientWithFilters(f map[string][]string) (Client, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &dockerClient{cli, filterArgs, "localhost"}, nil
|
return &dockerClient{cli, filterArgs, &Host{Name: "localhost", Host: "localhost"}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClientWithTlsAndFilter(f map[string][]string, connection string) (Client, error) {
|
func NewClientWithTlsAndFilter(f map[string][]string, host Host) (Client, error) {
|
||||||
filterArgs := filters.NewArgs()
|
filterArgs := filters.NewArgs()
|
||||||
for key, values := range f {
|
for key, values := range f {
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
@@ -102,38 +100,19 @@ func NewClientWithTlsAndFilter(f map[string][]string, connection string) (Client
|
|||||||
|
|
||||||
log.Debugf("filterArgs = %v", filterArgs)
|
log.Debugf("filterArgs = %v", filterArgs)
|
||||||
|
|
||||||
remoteUrl, err := url.Parse(connection)
|
if host.URL.Scheme != "tcp" {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if remoteUrl.Scheme != "tcp" {
|
|
||||||
log.Fatal("Only tcp scheme is supported")
|
log.Fatal("Only tcp scheme is supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
host := remoteUrl.Hostname()
|
|
||||||
basePath, err := filepath.Abs("./certs")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("error converting certs path to absolute: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(filepath.Join(basePath, host)); !os.IsNotExist(err) {
|
|
||||||
basePath = filepath.Join(basePath, host)
|
|
||||||
}
|
|
||||||
|
|
||||||
cacertPath := filepath.Join(basePath, "ca.pem")
|
|
||||||
certPath := filepath.Join(basePath, "cert.pem")
|
|
||||||
keyPath := filepath.Join(basePath, "key.pem")
|
|
||||||
|
|
||||||
opts := []client.Opt{
|
opts := []client.Opt{
|
||||||
client.WithHost(connection),
|
client.WithHost(host.URL.String()),
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Stat(cacertPath); os.IsNotExist(err) {
|
if host.ValidCerts {
|
||||||
log.Debugf("%s does not exist, using plain HTTP", cacertPath)
|
log.Debugf("Using TLS client config with certs at: %s", filepath.Dir(host.CertPath))
|
||||||
|
opts = append(opts, client.WithTLSClientConfig(host.CACertPath, host.CertPath, host.KeyPath))
|
||||||
} else {
|
} else {
|
||||||
log.Debugf("Using TLS client config with certs at: %s", basePath)
|
log.Debugf("No valid certs found, using plain TCP")
|
||||||
opts = append(opts, client.WithTLSClientConfig(cacertPath, certPath, keyPath))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
opts = append(opts, client.WithAPIVersionNegotiation())
|
opts = append(opts, client.WithAPIVersionNegotiation())
|
||||||
@@ -144,7 +123,7 @@ func NewClientWithTlsAndFilter(f map[string][]string, connection string) (Client
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &dockerClient{cli, filterArgs, host}, nil
|
return &dockerClient{cli, filterArgs, &host}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dockerClient) FindContainer(id string) (Container, error) {
|
func (d *dockerClient) FindContainer(id string) (Container, error) {
|
||||||
@@ -191,7 +170,7 @@ func (d *dockerClient) ListContainers() ([]Container, error) {
|
|||||||
Created: c.Created,
|
Created: c.Created,
|
||||||
State: c.State,
|
State: c.State,
|
||||||
Status: c.Status,
|
Status: c.Status,
|
||||||
Host: d.host,
|
Host: d.host.Host,
|
||||||
Health: findBetweenParentheses(c.Status),
|
Health: findBetweenParentheses(c.Status),
|
||||||
}
|
}
|
||||||
containers = append(containers, container)
|
containers = append(containers, container)
|
||||||
@@ -308,7 +287,7 @@ func (d *dockerClient) Events(ctx context.Context, messages chan<- ContainerEven
|
|||||||
messages <- ContainerEvent{
|
messages <- ContainerEvent{
|
||||||
ActorID: message.Actor.ID[:12],
|
ActorID: message.Actor.ID[:12],
|
||||||
Name: message.Action,
|
Name: message.Action,
|
||||||
Host: d.host,
|
Host: d.host.Host,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -371,7 +350,7 @@ func (d *dockerClient) Ping(ctx context.Context) (types.Ping, error) {
|
|||||||
return d.cli.Ping(ctx)
|
return d.cli.Ping(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dockerClient) Host() string {
|
func (d *dockerClient) Host() *Host {
|
||||||
return d.host
|
return d.host
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func (m *mockedProxy) ContainerStats(ctx context.Context, containerID string, st
|
|||||||
func Test_dockerClient_ListContainers_null(t *testing.T) {
|
func Test_dockerClient_ListContainers_null(t *testing.T) {
|
||||||
proxy := new(mockedProxy)
|
proxy := new(mockedProxy)
|
||||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, nil)
|
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, nil)
|
||||||
client := &dockerClient{proxy, filters.NewArgs(), "localhost"}
|
client := &dockerClient{proxy, filters.NewArgs(), &Host{Host: "localhost"}}
|
||||||
|
|
||||||
list, err := client.ListContainers()
|
list, err := client.ListContainers()
|
||||||
assert.Empty(t, list, "list should be empty")
|
assert.Empty(t, list, "list should be empty")
|
||||||
@@ -66,7 +66,7 @@ func Test_dockerClient_ListContainers_null(t *testing.T) {
|
|||||||
func Test_dockerClient_ListContainers_error(t *testing.T) {
|
func Test_dockerClient_ListContainers_error(t *testing.T) {
|
||||||
proxy := new(mockedProxy)
|
proxy := new(mockedProxy)
|
||||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, errors.New("test"))
|
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, errors.New("test"))
|
||||||
client := &dockerClient{proxy, filters.NewArgs(), "localhost"}
|
client := &dockerClient{proxy, filters.NewArgs(), &Host{Host: "localhost"}}
|
||||||
|
|
||||||
list, err := client.ListContainers()
|
list, err := client.ListContainers()
|
||||||
assert.Nil(t, list, "list should be nil")
|
assert.Nil(t, list, "list should be nil")
|
||||||
@@ -89,7 +89,7 @@ func Test_dockerClient_ListContainers_happy(t *testing.T) {
|
|||||||
|
|
||||||
proxy := new(mockedProxy)
|
proxy := new(mockedProxy)
|
||||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
|
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
|
||||||
client := &dockerClient{proxy, filters.NewArgs(), "localhost"}
|
client := &dockerClient{proxy, filters.NewArgs(), &Host{Host: "localhost"}}
|
||||||
|
|
||||||
list, err := client.ListContainers()
|
list, err := client.ListContainers()
|
||||||
require.NoError(t, err, "error should not return an error.")
|
require.NoError(t, err, "error should not return an error.")
|
||||||
@@ -129,7 +129,7 @@ func Test_dockerClient_ContainerLogs_happy(t *testing.T) {
|
|||||||
json := types.ContainerJSON{Config: &container.Config{Tty: false}}
|
json := types.ContainerJSON{Config: &container.Config{Tty: false}}
|
||||||
proxy.On("ContainerInspect", mock.Anything, id).Return(json, nil)
|
proxy.On("ContainerInspect", mock.Anything, id).Return(json, nil)
|
||||||
|
|
||||||
client := &dockerClient{proxy, filters.NewArgs(), "localhost"}
|
client := &dockerClient{proxy, filters.NewArgs(), &Host{Host: "localhost"}}
|
||||||
logReader, _ := client.ContainerLogs(context.Background(), id, "since", STDALL)
|
logReader, _ := client.ContainerLogs(context.Background(), id, "since", STDALL)
|
||||||
|
|
||||||
actual, _ := io.ReadAll(logReader)
|
actual, _ := io.ReadAll(logReader)
|
||||||
@@ -150,7 +150,7 @@ func Test_dockerClient_ContainerLogs_happy_with_tty(t *testing.T) {
|
|||||||
json := types.ContainerJSON{Config: &container.Config{Tty: true}}
|
json := types.ContainerJSON{Config: &container.Config{Tty: true}}
|
||||||
proxy.On("ContainerInspect", mock.Anything, id).Return(json, nil)
|
proxy.On("ContainerInspect", mock.Anything, id).Return(json, nil)
|
||||||
|
|
||||||
client := &dockerClient{proxy, filters.NewArgs(), "localhost"}
|
client := &dockerClient{proxy, filters.NewArgs(), &Host{Host: "localhost"}}
|
||||||
logReader, _ := client.ContainerLogs(context.Background(), id, "", STDALL)
|
logReader, _ := client.ContainerLogs(context.Background(), id, "", STDALL)
|
||||||
|
|
||||||
actual, _ := io.ReadAll(logReader)
|
actual, _ := io.ReadAll(logReader)
|
||||||
@@ -165,7 +165,7 @@ func Test_dockerClient_ContainerLogs_error(t *testing.T) {
|
|||||||
|
|
||||||
proxy.On("ContainerLogs", mock.Anything, id, mock.Anything).Return(nil, errors.New("test"))
|
proxy.On("ContainerLogs", mock.Anything, id, mock.Anything).Return(nil, errors.New("test"))
|
||||||
|
|
||||||
client := &dockerClient{proxy, filters.NewArgs(), "localhost"}
|
client := &dockerClient{proxy, filters.NewArgs(), &Host{Host: "localhost"}}
|
||||||
|
|
||||||
reader, err := client.ContainerLogs(context.Background(), id, "", STDALL)
|
reader, err := client.ContainerLogs(context.Background(), id, "", STDALL)
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@ func Test_dockerClient_FindContainer_happy(t *testing.T) {
|
|||||||
|
|
||||||
proxy := new(mockedProxy)
|
proxy := new(mockedProxy)
|
||||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
|
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
|
||||||
client := &dockerClient{proxy, filters.NewArgs(), "localhost"}
|
client := &dockerClient{proxy, filters.NewArgs(), &Host{Host: "localhost"}}
|
||||||
|
|
||||||
container, err := client.FindContainer("abcdefghijkl")
|
container, err := client.FindContainer("abcdefghijkl")
|
||||||
require.NoError(t, err, "error should not be thrown")
|
require.NoError(t, err, "error should not be thrown")
|
||||||
@@ -216,7 +216,7 @@ func Test_dockerClient_FindContainer_error(t *testing.T) {
|
|||||||
|
|
||||||
proxy := new(mockedProxy)
|
proxy := new(mockedProxy)
|
||||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
|
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
|
||||||
client := &dockerClient{proxy, filters.NewArgs(), "localhost"}
|
client := &dockerClient{proxy, filters.NewArgs(), &Host{Host: "localhost"}}
|
||||||
|
|
||||||
_, err := client.FindContainer("not_valid")
|
_, err := client.FindContainer("not_valid")
|
||||||
require.Error(t, err, "error should be thrown")
|
require.Error(t, err, "error should be thrown")
|
||||||
|
|||||||
68
docker/host.go
Normal file
68
docker/host.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Host struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
URL *url.URL `json:"-"`
|
||||||
|
CertPath string `json:"-"`
|
||||||
|
CACertPath string `json:"-"`
|
||||||
|
KeyPath string `json:"-"`
|
||||||
|
ValidCerts bool `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseConnection(connection string) (Host, error) {
|
||||||
|
parts := strings.Split(connection, "|")
|
||||||
|
if len(parts) > 2 {
|
||||||
|
return Host{}, fmt.Errorf("invalid connection string: %s", connection)
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteUrl, err := url.Parse(parts[0])
|
||||||
|
if err != nil {
|
||||||
|
return Host{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
name := remoteUrl.Hostname()
|
||||||
|
if len(parts) == 2 {
|
||||||
|
name = parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
basePath, err := filepath.Abs("./certs")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error converting certs path to absolute: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
host := remoteUrl.Hostname()
|
||||||
|
if _, err := os.Stat(filepath.Join(basePath, host)); !os.IsNotExist(err) {
|
||||||
|
basePath = filepath.Join(basePath, host)
|
||||||
|
}
|
||||||
|
|
||||||
|
cacertPath := filepath.Join(basePath, "ca.pem")
|
||||||
|
certPath := filepath.Join(basePath, "cert.pem")
|
||||||
|
keyPath := filepath.Join(basePath, "key.pem")
|
||||||
|
|
||||||
|
hasCerts := true
|
||||||
|
if _, err := os.Stat(cacertPath); os.IsNotExist(err) {
|
||||||
|
cacertPath = ""
|
||||||
|
hasCerts = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return Host{
|
||||||
|
Name: name,
|
||||||
|
Host: host,
|
||||||
|
URL: remoteUrl,
|
||||||
|
CertPath: certPath,
|
||||||
|
CACertPath: cacertPath,
|
||||||
|
KeyPath: keyPath,
|
||||||
|
ValidCerts: hasCerts,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
4
docs/components.d.ts
vendored
4
docs/components.d.ts
vendored
@@ -3,11 +3,9 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
// Generated by unplugin-vue-components
|
// Generated by unplugin-vue-components
|
||||||
// Read more: https://github.com/vuejs/core/pull/3399
|
// Read more: https://github.com/vuejs/core/pull/3399
|
||||||
import '@vue/runtime-core'
|
|
||||||
|
|
||||||
export {}
|
export {}
|
||||||
|
|
||||||
declare module '@vue/runtime-core' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
BrowserWindow: typeof import('./.vitepress/theme/components/BrowserWindow.vue')['default']
|
BrowserWindow: typeof import('./.vitepress/theme/components/BrowserWindow.vue')['default']
|
||||||
HeroVideo: typeof import('./.vitepress/theme/components/HeroVideo.vue')['default']
|
HeroVideo: typeof import('./.vitepress/theme/components/HeroVideo.vue')['default']
|
||||||
|
|||||||
@@ -53,3 +53,29 @@ docker run --volume=/var/run/docker.sock:/var/run/docker.sock -p 8080:8080 amir2
|
|||||||
::: warning
|
::: warning
|
||||||
Exposing `docker.sock` publicly is not safe. Only use a proxy for an internal network where all clients are trusted.
|
Exposing `docker.sock` publicly is not safe. Only use a proxy for an internal network where all clients are trusted.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
## Adding labels to hosts
|
||||||
|
|
||||||
|
`--remote-host` supports host labels by appending them to the connection string with `|`. For example, `--remote-host tcp://123.1.1.1:2375|foobar.com` will use foobar.com as the label in the UI. A full example of this using the CLI or compose are:
|
||||||
|
|
||||||
|
::: code-group
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker run --volume=/var/run/docker.sock:/var/run/docker.sock -p 8080:8080 amir20/dozzle --remote-host tcp://123.1.1.1:2375|foobar.com
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml [docker-compose.yml]
|
||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
dozzle:
|
||||||
|
image: amir20/dozzle:latest
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- /path/to/certs:/certs
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
environment:
|
||||||
|
DOZZLE_REMOTE_HOST: tcp://167.99.1.1:2376|foo.com,tcp://167.99.1.2:2376|bar.com
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|||||||
19
main.go
19
main.go
@@ -137,24 +137,29 @@ func doStartEvent(arg args) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createClients(args args, localClientFactory func(map[string][]string) (docker.Client, error), remoteClientFactory func(map[string][]string, string) (docker.Client, error)) map[string]docker.Client {
|
func createClients(args args, localClientFactory func(map[string][]string) (docker.Client, error), remoteClientFactory func(map[string][]string, docker.Host) (docker.Client, error)) map[string]docker.Client {
|
||||||
clients := make(map[string]docker.Client)
|
clients := make(map[string]docker.Client)
|
||||||
|
|
||||||
if localClient := createLocalClient(args, localClientFactory); localClient != nil {
|
if localClient := createLocalClient(args, localClientFactory); localClient != nil {
|
||||||
clients[localClient.Host()] = localClient
|
clients[localClient.Host().Host] = localClient
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, host := range args.RemoteHost {
|
for _, remoteHost := range args.RemoteHost {
|
||||||
log.Infof("Creating client for %s", host)
|
host, err := docker.ParseConnection(remoteHost)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Could not parse remote host %s: %s", remoteHost, err)
|
||||||
|
}
|
||||||
|
log.Debugf("Creating remote client for %s with %+v", host.Name, host)
|
||||||
|
log.Infof("Creating client for %s with %s", host.Name, host.URL.String())
|
||||||
if client, err := remoteClientFactory(args.Filter, host); err == nil {
|
if client, err := remoteClientFactory(args.Filter, host); err == nil {
|
||||||
if _, err := client.ListContainers(); err == nil {
|
if _, err := client.ListContainers(); err == nil {
|
||||||
log.Debugf("Connected to local Docker Engine")
|
log.Debugf("Connected to local Docker Engine")
|
||||||
clients[client.Host()] = client
|
clients[client.Host().Host] = client
|
||||||
} else {
|
} else {
|
||||||
log.Warnf("Could not connect to remote host %s: %s", host, err)
|
log.Warnf("Could not connect to remote host %s: %s", host.Host, err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Warnf("Could not create client for %s: %s", host, err)
|
log.Warnf("Could not create client for %s: %s", host.Host, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
42
main_test.go
42
main_test.go
@@ -19,16 +19,18 @@ func (f *fakeClient) ListContainers() ([]docker.Container, error) {
|
|||||||
return args.Get(0).([]docker.Container), args.Error(1)
|
return args.Get(0).([]docker.Container), args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeClient) Host() string {
|
func (f *fakeClient) Host() *docker.Host {
|
||||||
args := f.Called()
|
args := f.Called()
|
||||||
return args.String(0)
|
return args.Get(0).(*docker.Host)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_valid_localhost(t *testing.T) {
|
func Test_valid_localhost(t *testing.T) {
|
||||||
fakeClientFactory := func(filter map[string][]string) (docker.Client, error) {
|
fakeClientFactory := func(filter map[string][]string) (docker.Client, error) {
|
||||||
client := new(fakeClient)
|
client := new(fakeClient)
|
||||||
client.On("ListContainers").Return([]docker.Container{}, nil)
|
client.On("ListContainers").Return([]docker.Container{}, nil)
|
||||||
client.On("Host").Return("localhost")
|
client.On("Host").Return(&docker.Host{
|
||||||
|
Host: "localhost",
|
||||||
|
})
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +45,9 @@ func Test_invalid_localhost(t *testing.T) {
|
|||||||
fakeClientFactory := func(filter map[string][]string) (docker.Client, error) {
|
fakeClientFactory := func(filter map[string][]string) (docker.Client, error) {
|
||||||
client := new(fakeClient)
|
client := new(fakeClient)
|
||||||
client.On("ListContainers").Return([]docker.Container{}, errors.New("error"))
|
client.On("ListContainers").Return([]docker.Container{}, errors.New("error"))
|
||||||
client.On("Host").Return("localhost")
|
client.On("Host").Return(&docker.Host{
|
||||||
|
Host: "localhost",
|
||||||
|
})
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,15 +62,19 @@ func Test_valid_remote(t *testing.T) {
|
|||||||
fakeLocalClientFactory := func(filter map[string][]string) (docker.Client, error) {
|
fakeLocalClientFactory := func(filter map[string][]string) (docker.Client, error) {
|
||||||
client := new(fakeClient)
|
client := new(fakeClient)
|
||||||
client.On("ListContainers").Return([]docker.Container{}, errors.New("error"))
|
client.On("ListContainers").Return([]docker.Container{}, errors.New("error"))
|
||||||
client.On("Host").Return("localhost")
|
client.On("Host").Return(&docker.Host{
|
||||||
|
Host: "localhost",
|
||||||
|
})
|
||||||
|
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fakeRemoteClientFactory := func(filter map[string][]string, host string) (docker.Client, error) {
|
fakeRemoteClientFactory := func(filter map[string][]string, host docker.Host) (docker.Client, error) {
|
||||||
client := new(fakeClient)
|
client := new(fakeClient)
|
||||||
client.On("ListContainers").Return([]docker.Container{}, nil)
|
client.On("ListContainers").Return([]docker.Container{}, nil)
|
||||||
client.On("Host").Return("test")
|
client.On("Host").Return(&docker.Host{
|
||||||
|
Host: "test",
|
||||||
|
})
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,14 +93,18 @@ func Test_valid_remote_and_local(t *testing.T) {
|
|||||||
fakeLocalClientFactory := func(filter map[string][]string) (docker.Client, error) {
|
fakeLocalClientFactory := func(filter map[string][]string) (docker.Client, error) {
|
||||||
client := new(fakeClient)
|
client := new(fakeClient)
|
||||||
client.On("ListContainers").Return([]docker.Container{}, nil)
|
client.On("ListContainers").Return([]docker.Container{}, nil)
|
||||||
client.On("Host").Return("localhost")
|
client.On("Host").Return(&docker.Host{
|
||||||
|
Host: "localhost",
|
||||||
|
})
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fakeRemoteClientFactory := func(filter map[string][]string, host string) (docker.Client, error) {
|
fakeRemoteClientFactory := func(filter map[string][]string, host docker.Host) (docker.Client, error) {
|
||||||
client := new(fakeClient)
|
client := new(fakeClient)
|
||||||
client.On("ListContainers").Return([]docker.Container{}, nil)
|
client.On("ListContainers").Return([]docker.Container{}, nil)
|
||||||
client.On("Host").Return("test")
|
client.On("Host").Return(&docker.Host{
|
||||||
|
Host: "test",
|
||||||
|
})
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,13 +123,17 @@ func Test_no_clients(t *testing.T) {
|
|||||||
fakeLocalClientFactory := func(filter map[string][]string) (docker.Client, error) {
|
fakeLocalClientFactory := func(filter map[string][]string) (docker.Client, error) {
|
||||||
client := new(fakeClient)
|
client := new(fakeClient)
|
||||||
client.On("ListContainers").Return([]docker.Container{}, errors.New("error"))
|
client.On("ListContainers").Return([]docker.Container{}, errors.New("error"))
|
||||||
client.On("Host").Return("localhost")
|
client.On("Host").Return(&docker.Host{
|
||||||
|
Host: "localhost",
|
||||||
|
})
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fakeRemoteClientFactory := func(filter map[string][]string, host string) (docker.Client, error) {
|
fakeRemoteClientFactory := func(filter map[string][]string, host docker.Host) (docker.Client, error) {
|
||||||
client := new(fakeClient)
|
client := new(fakeClient)
|
||||||
client.On("Host").Return("test")
|
client.On("Host").Return(&docker.Host{
|
||||||
|
Host: "test",
|
||||||
|
})
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -154,26 +155,21 @@ func (h *handler) executeTemplate(w http.ResponseWriter, req *http.Request) {
|
|||||||
path = h.config.Base
|
path = h.config.Base
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all keys from hosts map
|
hosts := make([]*docker.Host, 0, len(h.clients))
|
||||||
hosts := make([]string, 0, len(h.clients))
|
for _, v := range h.clients {
|
||||||
for k := range h.clients {
|
hosts = append(hosts, v.Host())
|
||||||
hosts = append(hosts, k)
|
|
||||||
}
|
}
|
||||||
|
sort.Slice(hosts, func(i, j int) bool {
|
||||||
|
return hosts[i].Name < hosts[j].Name
|
||||||
|
})
|
||||||
|
|
||||||
config := struct {
|
config := map[string]interface{}{
|
||||||
Base string `json:"base"`
|
"base": path,
|
||||||
Version string `json:"version"`
|
"version": h.config.Version,
|
||||||
AuthorizationNeeded bool `json:"authorizationNeeded"`
|
"authorizationNeeded": h.isAuthorizationNeeded(req),
|
||||||
Secured bool `json:"secured"`
|
"secured": secured,
|
||||||
Hostname string `json:"hostname"`
|
"hostname": h.config.Hostname,
|
||||||
Hosts []string `json:"hosts"`
|
"hosts": hosts,
|
||||||
}{
|
|
||||||
path,
|
|
||||||
h.config.Version,
|
|
||||||
h.isAuthorizationNeeded(req),
|
|
||||||
secured,
|
|
||||||
h.config.Hostname,
|
|
||||||
hosts,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import (
|
|||||||
func Test_createRoutes_index(t *testing.T) {
|
func Test_createRoutes_index(t *testing.T) {
|
||||||
fs := afero.NewMemMapFs()
|
fs := afero.NewMemMapFs()
|
||||||
require.NoError(t, afero.WriteFile(fs, "index.html", []byte("index page"), 0644), "WriteFile should have no error.")
|
require.NoError(t, afero.WriteFile(fs, "index.html", []byte("index page"), 0644), "WriteFile should have no error.")
|
||||||
|
|
||||||
handler := createHandler(nil, afero.NewIOFS(fs), Config{Base: "/"})
|
handler := createHandler(nil, afero.NewIOFS(fs), Config{Base: "/"})
|
||||||
req, err := http.NewRequest("GET", "/", nil)
|
req, err := http.NewRequest("GET", "/", nil)
|
||||||
require.NoError(t, err, "NewRequest should not return an error.")
|
require.NoError(t, err, "NewRequest should not return an error.")
|
||||||
@@ -64,6 +65,7 @@ func Test_createRoutes_redirect_with_auth(t *testing.T) {
|
|||||||
func Test_createRoutes_foobar(t *testing.T) {
|
func Test_createRoutes_foobar(t *testing.T) {
|
||||||
fs := afero.NewMemMapFs()
|
fs := afero.NewMemMapFs()
|
||||||
require.NoError(t, afero.WriteFile(fs, "index.html", []byte("foo page"), 0644), "WriteFile should have no error.")
|
require.NoError(t, afero.WriteFile(fs, "index.html", []byte("foo page"), 0644), "WriteFile should have no error.")
|
||||||
|
|
||||||
handler := createHandler(nil, afero.NewIOFS(fs), Config{Base: "/foobar"})
|
handler := createHandler(nil, afero.NewIOFS(fs), Config{Base: "/foobar"})
|
||||||
req, err := http.NewRequest("GET", "/foobar/", nil)
|
req, err := http.NewRequest("GET", "/foobar/", nil)
|
||||||
require.NoError(t, err, "NewRequest should not return an error.")
|
require.NoError(t, err, "NewRequest should not return an error.")
|
||||||
|
|||||||
@@ -49,15 +49,18 @@ func (m *MockedClient) ContainerLogsBetweenDates(ctx context.Context, id string,
|
|||||||
return args.Get(0).(io.ReadCloser), args.Error(1)
|
return args.Get(0).(io.ReadCloser), args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockedClient) Host() string {
|
func (m *MockedClient) Host() *docker.Host {
|
||||||
args := m.Called()
|
args := m.Called()
|
||||||
return args.String(0)
|
return args.Get(0).(*docker.Host)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createHandler(client docker.Client, content fs.FS, config Config) *chi.Mux {
|
func createHandler(client docker.Client, content fs.FS, config Config) *chi.Mux {
|
||||||
if client == nil {
|
if client == nil {
|
||||||
client = new(MockedClient)
|
client = new(MockedClient)
|
||||||
client.(*MockedClient).On("ListContainers").Return([]docker.Container{}, nil)
|
client.(*MockedClient).On("ListContainers").Return([]docker.Container{}, nil)
|
||||||
|
client.(*MockedClient).On("Host").Return(&docker.Host{
|
||||||
|
Host: "localhost",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if content == nil {
|
if content == nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user