Repository: amir20/dozzle (language: go)

Issue:
Filter combined Output of hosts
### Describe the feature you would like to see

We can filter fot STDOUT and STDERR on a per container basis but i would love to be able to filter on the per stack level.

### Describe how you would like to see this feature implemented

Kinda of exactly the same, with the little blue and red button in the "Stat-Bar", it fits the style and is easy and comprehensive for everyone.

### Describe any alternatives you've considered

_No response_


Discussion / hints:
It's actually implemented in the API just need to do the drop down in UI. 

Fix patch:
diff --git a/assets/components.d.ts b/assets/components.d.ts
index be09a019fb9a..4f6e7e14e7b1 100644
--- a/assets/components.d.ts
+++ b/assets/components.d.ts
@@ -71,6 +71,7 @@ declare module 'vue' {
     'Mdi:magnify': typeof import('~icons/mdi/magnify')['default']
     'Mdi:satelliteVariant': typeof import('~icons/mdi/satellite-variant')['default']
     MobileMenu: typeof import('./components/common/MobileMenu.vue')['default']
+    MultiContainerActionToolbar: typeof import('./components/LogViewer/MultiContainerActionToolbar.vue')['default']
     MultiContainerLog: typeof import('./components/MultiContainerViewer/MultiContainerLog.vue')['default']
     MultiContainerStat: typeof import('./components/LogViewer/MultiContainerStat.vue')['default']
     'Octicon:container24': typeof import('~icons/octicon/container24')['default']
diff --git a/assets/components/GroupedViewer/GroupedLog.vue b/assets/components/GroupedViewer/GroupedLog.vue
index c1265a677a98..e8b1f3bc7c75 100644
--- a/assets/components/GroupedViewer/GroupedLog.vue
+++ b/assets/components/GroupedViewer/GroupedLog.vue
@@ -8,6 +8,7 @@
           </div>
         </div>
         <MultiContainerStat class="ml-auto" :containers="group.containers" />
+        <MultiContainerActionToolbar @clear="viewer?.clear()" />
       </div>
     </template>
     <template #default>
@@ -25,6 +26,7 @@
 <script lang="ts" setup>
 import ViewerWithSource from "@/components/LogViewer/ViewerWithSource.vue";
 import { GroupedContainers } from "@/models/Container";
+import { ComponentExposed } from "vue-component-type-helpers";
 
 const { name, scrollable = false } = defineProps<{
   name: string;
@@ -32,6 +34,7 @@ const { name, scrollable = false } = defineProps<{
 }>();
 
 const containerStore = useContainerStore();
+const viewer = ref<ComponentExposed<typeof ViewerWithSource>>();
 
 const { ready } = storeToRefs(containerStore);
 
diff --git a/assets/components/LogViewer/MultiContainerActionToolbar.vue b/assets/components/LogViewer/MultiContainerActionToolbar.vue
new file mode 100644
index 000000000000..9f55b4716eaa
--- /dev/null
+++ b/assets/components/LogViewer/MultiContainerActionToolbar.vue
@@ -0,0 +1,83 @@
+<template>
+  <div class="dropdown dropdown-end dropdown-hover">
+    <label tabindex="0" class="btn btn-ghost btn-sm gap-0.5 px-2">
+      <carbon:circle-solid class="w-2.5 text-red" v-if="streamConfig.stderr" />
+      <carbon:circle-solid class="w-2.5 text-blue" v-if="streamConfig.stdout" />
+    </label>
+    <ul tabindex="0" class="menu dropdown-content z-50 w-52 rounded-box bg-base p-1 shadow">
+      <li>
+        <a @click.prevent="clear()">
+          <octicon:trash-24 /> {{ $t("toolbar.clear") }}
+          <KeyShortcut char="k" :modifiers="['shift', 'meta']" />
+        </a>
+      </li>
+      <li>
+        <a @click.prevent="showSearch = true">
+          <mdi:magnify /> {{ $t("toolbar.search") }}
+          <KeyShortcut char="f" />
+        </a>
+      </li>
+      <li class="line"></li>
+      <li>
+        <a
+          @click="
+            streamConfig.stdout = true;
+            streamConfig.stderr = true;
+          "
+        >
+          <div class="flex size-4 gap-0.5">
+            <template v-if="streamConfig.stderr && streamConfig.stdout">
+              <carbon:circle-solid class="w-2 text-red" />
+              <carbon:circle-solid class="w-2 text-blue" />
+            </template>
+          </div>
+          {{ $t("toolbar.show-all") }}
+        </a>
+      </li>
+      <li>
+        <a
+          @click="
+            streamConfig.stdout = true;
+            streamConfig.stderr = false;
+          "
+        >
+          <div class="flex size-4 flex-col gap-1">
+            <carbon:circle-solid class="w-2 text-blue" v-if="!streamConfig.stderr && streamConfig.stdout" />
+          </div>
+          {{ $t("toolbar.show", { std: "STDOUT" }) }}
+        </a>
+      </li>
+      <li>
+        <a
+          @click="
+            streamConfig.stdout = false;
+            streamConfig.stderr = true;
+          "
+        >
+          <div class="flex size-4 flex-col gap-1">
+            <carbon:circle-solid class="w-2 text-red" v-if="streamConfig.stderr && !streamConfig.stdout" />
+          </div>
+          {{ $t("toolbar.show", { std: "STDERR" }) }}
+        </a>
+      </li>
+    </ul>
+  </div>
+</template>
+
+<script lang="ts" setup>
+const { showSearch } = useSearchFilter();
+
+const clear = defineEmit();
+
+const { streamConfig } = useLoggingContext();
+</script>
+
+<style scoped lang="postcss">
+li.line {
+  @apply h-px bg-base-content/20;
+}
+
+a {
+  @apply whitespace-nowrap;
+}
+</style>
diff --git a/assets/components/ServiceViewer/ServiceLog.vue b/assets/components/ServiceViewer/ServiceLog.vue
index acdea3fd65c8..3006f44e267f 100644
--- a/assets/components/ServiceViewer/ServiceLog.vue
+++ b/assets/components/ServiceViewer/ServiceLog.vue
@@ -11,6 +11,7 @@
           </Tag>
         </div>
         <MultiContainerStat class="ml-auto" :containers="service.containers" />
+        <MultiContainerActionToolbar @clear="viewer?.clear()" />
       </div>
     </template>
     <template #default>
@@ -28,6 +29,7 @@
 <script lang="ts" setup>
 import { Service } from "@/models/Stack";
 import ViewerWithSource from "@/components/LogViewer/ViewerWithSource.vue";
+import { ComponentExposed } from "vue-component-type-helpers";
 
 const { name, scrollable = false } = defineProps<{
   scrollable?: boolean;
@@ -35,7 +37,7 @@ const { name, scrollable = false } = defineProps<{
 }>();
 
 const visibleKeys = ref<string[][]>([]);
-
+const viewer = ref<ComponentExposed<typeof ViewerWithSource>>();
 const store = useSwarmStore();
 const { services } = storeToRefs(store) as unknown as { services: Ref<Service[]> };
 const service = computed(() => services.value.find((s) => s.name === name) ?? new Service("", []));
diff --git a/assets/components/StackViewer/StackLog.vue b/assets/components/StackViewer/StackLog.vue
index 8e48ac4bad45..4df5f31889de 100644
--- a/assets/components/StackViewer/StackLog.vue
+++ b/assets/components/StackViewer/StackLog.vue
@@ -14,6 +14,7 @@
           </Tag>
         </div>
         <MultiContainerStat class="ml-auto" :containers="stack.containers" />
+        <MultiContainerActionToolbar @clear="viewer?.clear()" />
       </div>
     </template>
     <template #default>
@@ -31,14 +32,14 @@
 <script lang="ts" setup>
 import { Stack } from "@/models/Stack";
 import ViewerWithSource from "@/components/LogViewer/ViewerWithSource.vue";
-
+import { ComponentExposed } from "vue-component-type-helpers";
 const { name, scrollable = false } = defineProps<{
   scrollable?: boolean;
   name: string;
 }>();
 
 const visibleKeys = ref<string[][]>([]);
-
+const viewer = ref<ComponentExposed<typeof ViewerWithSource>>();
 const store = useSwarmStore();
 const { stacks } = storeToRefs(store) as unknown as { stacks: Ref<Stack[]> };
 const stack = computed(() => stacks.value.find((s) => s.name === name) ?? new Stack("", [], []));
diff --git a/internal/docker/client.go b/internal/docker/client.go
index a87e9ea81c8e..3a9a16f7d3cf 100644
--- a/internal/docker/client.go
+++ b/internal/docker/client.go
@@ -389,8 +389,6 @@ func newContainerFromJSON(c types.ContainerJSON, host string) Container {
 		group = c.Config.Labels["dev.dozzle.group"]
 	}
 
-	log.Debugf("newContainerFromJSON: %s", c.Config.Image)
-
 	container := Container{
 		ID:      c.ID[:12],
 		Name:    name,


Test patch: