Skip to content

Commit

Permalink
feat: supports swarm mode with stacks and services on remote hosts 🙌🏼 (
Browse files Browse the repository at this point in the history
  • Loading branch information
amir20 committed May 23, 2024
1 parent 68908e5 commit ba0206a
Show file tree
Hide file tree
Showing 89 changed files with 1,928 additions and 997 deletions.
58 changes: 53 additions & 5 deletions assets/auto-imports.d.ts

Large diffs are not rendered by default.

32 changes: 22 additions & 10 deletions assets/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
BarChart: typeof import('./components/BarChart.vue')['default']
'Carbon:caretDown': typeof import('~icons/carbon/caret-down')['default']
'Carbon:circleSolid': typeof import('~icons/carbon/circle-solid')['default']
'Carbon:copyFile': typeof import('~icons/carbon/copy-file')['default']
Expand All @@ -25,34 +24,35 @@ declare module 'vue' {
'Cil:columns': typeof import('~icons/cil/columns')['default']
'Cil:xCircle': typeof import('~icons/cil/x-circle')['default']
ComplexLogItem: typeof import('./components/LogViewer/ComplexLogItem.vue')['default']
ContainerHealth: typeof import('./components/LogViewer/ContainerHealth.vue')['default']
ContainerLogViewer: typeof import('./components/LogViewer/ContainerLogViewer.vue')['default']
ContainerPopup: typeof import('./components/LogViewer/ContainerPopup.vue')['default']
ContainerStat: typeof import('./components/LogViewer/ContainerStat.vue')['default']
ContainerActionsToolbar: typeof import('./components/ContainerViewer/ContainerActionsToolbar.vue')['default']
ContainerHealth: typeof import('./components/ContainerViewer/ContainerHealth.vue')['default']
ContainerLog: typeof import('./components/ContainerViewer/ContainerLog.vue')['default']
ContainerName: typeof import('./components/LogViewer/ContainerName.vue')['default']
ContainerPopup: typeof import('./components/ContainerPopup.vue')['default']
ContainerTable: typeof import('./components/ContainerTable.vue')['default']
ContainerTitle: typeof import('./components/LogViewer/ContainerTitle.vue')['default']
ContainerTitle: typeof import('./components/ContainerViewer/ContainerTitle.vue')['default']
DateTime: typeof import('./components/common/DateTime.vue')['default']
DistanceTime: typeof import('./components/common/DistanceTime.vue')['default']
DockerEventLogItem: typeof import('./components/LogViewer/DockerEventLogItem.vue')['default']
Dropdown: typeof import('./components/common/Dropdown.vue')['default']
DropdownMenu: typeof import('./components/common/DropdownMenu.vue')['default']
EventSource: typeof import('./components/LogViewer/EventSource.vue')['default']
FieldList: typeof import('./components/LogViewer/FieldList.vue')['default']
FuzzySearchModal: typeof import('./components/FuzzySearchModal.vue')['default']
GroupedLog: typeof import('./components/GroupedViewer/GroupedLog.vue')['default']
HostList: typeof import('./components/HostList.vue')['default']
HostMenu: typeof import('./components/HostMenu.vue')['default']
'Ic:sharpKeyboardReturn': typeof import('~icons/ic/sharp-keyboard-return')['default']
InfiniteLoader: typeof import('./components/InfiniteLoader.vue')['default']
KeyShortcut: typeof import('./components/common/KeyShortcut.vue')['default']
LabeledInput: typeof import('./components/common/LabeledInput.vue')['default']
Links: typeof import('./components/Links.vue')['default']
LogActionsToolbar: typeof import('./components/LogViewer/LogActionsToolbar.vue')['default']
LogContainer: typeof import('./components/LogViewer/LogContainer.vue')['default']
LogDate: typeof import('./components/LogViewer/LogDate.vue')['default']
LogEventSource: typeof import('./components/LogViewer/LogEventSource.vue')['default']
LogLevel: typeof import('./components/LogViewer/LogLevel.vue')['default']
LogList: typeof import('./components/LogViewer/LogList.vue')['default']
LogMessageActions: typeof import('./components/LogViewer/LogMessageActions.vue')['default']
LogStd: typeof import('./components/LogViewer/LogStd.vue')['default']
LogViewer: typeof import('./components/LogViewer/LogViewer.vue')['default']
LogViewerWithSource: typeof import('./components/LogViewer/LogViewerWithSource.vue')['default']
'MaterialSymbols:collapseAllRounded': typeof import('~icons/material-symbols/collapse-all-rounded')['default']
'MaterialSymbols:expandAllRounded': typeof import('~icons/material-symbols/expand-all-rounded')['default']
'Mdi:announcement': typeof import('~icons/mdi/announcement')['default']
Expand All @@ -67,31 +67,43 @@ declare module 'vue' {
'Mdi:keyboardEsc': typeof import('~icons/mdi/keyboard-esc')['default']
'Mdi:magnify': typeof import('~icons/mdi/magnify')['default']
MobileMenu: typeof import('./components/common/MobileMenu.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']
'Octicon:download24': typeof import('~icons/octicon/download24')['default']
'Octicon:trash24': typeof import('~icons/octicon/trash24')['default']
PageWithLinks: typeof import('./components/PageWithLinks.vue')['default']
'Ph:arrowsMerge': typeof import('~icons/ph/arrows-merge')['default']
'Ph:boundingBoxFill': typeof import('~icons/ph/bounding-box-fill')['default']
'Ph:circlesFour': typeof import('~icons/ph/circles-four')['default']
'Ph:command': typeof import('~icons/ph/command')['default']
'Ph:computerTower': typeof import('~icons/ph/computer-tower')['default']
'Ph:controlBold': typeof import('~icons/ph/control-bold')['default']
'Ph:cpu': typeof import('~icons/ph/cpu')['default']
'Ph:memory': typeof import('~icons/ph/memory')['default']
'Ph:stack': typeof import('~icons/ph/stack')['default']
'Ph:stackSimple': typeof import('~icons/ph/stack-simple')['default']
Popup: typeof import('./components/Popup.vue')['default']
Releases: typeof import('./components/Releases.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
ScrollableView: typeof import('./components/ScrollableView.vue')['default']
ScrollProgress: typeof import('./components/ScrollProgress.vue')['default']
Search: typeof import('./components/Search.vue')['default']
ServiceLog: typeof import('./components/ServiceViewer/ServiceLog.vue')['default']
SideMenu: typeof import('./components/SideMenu.vue')['default']
SidePanel: typeof import('./components/SidePanel.vue')['default']
SimpleLogItem: typeof import('./components/LogViewer/SimpleLogItem.vue')['default']
SkippedEntriesLogItem: typeof import('./components/LogViewer/SkippedEntriesLogItem.vue')['default']
SlideTransition: typeof import('./components/common/SlideTransition.vue')['default']
StackLog: typeof import('./components/StackViewer/StackLog.vue')['default']
StatMonitor: typeof import('./components/LogViewer/StatMonitor.vue')['default']
StatSparkline: typeof import('./components/LogViewer/StatSparkline.vue')['default']
SwarmMenu: typeof import('./components/SwarmMenu.vue')['default']
Tag: typeof import('./components/common/Tag.vue')['default']
TimedButton: typeof import('./components/common/TimedButton.vue')['default']
Toggle: typeof import('./components/common/Toggle.vue')['default']
ViewerWithSource: typeof import('./components/LogViewer/ViewerWithSource.vue')['default']
ZigZag: typeof import('./components/LogViewer/ZigZag.vue')['default']
}
}
20 changes: 0 additions & 20 deletions assets/components/BarChart.vue

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div>
<span class="font-light capitalize"> RUNNING </span>
<span class="font-semibold">
<distance-time :date="container.created" strict :suffix="false"></distance-time>
<DistanceTime :date="container.created" strict :suffix="false" />
</span>
</div>
<div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<li>
<a @click.prevent="clear()">
<octicon:trash-24 /> {{ $t("toolbar.clear") }}
<key-shortcut char="k" :modifiers="['shift', 'meta']"></key-shortcut>
<KeyShortcut char="k" :modifiers="['shift', 'meta']" />
</a>
</li>
<li>
Expand All @@ -17,7 +17,7 @@
<li>
<a @click.prevent="showSearch = true">
<mdi:magnify /> {{ $t("toolbar.search") }}
<key-shortcut char="f"></key-shortcut>
<KeyShortcut char="f" />
</a>
</li>
<li class="line"></li>
Expand Down Expand Up @@ -101,15 +101,18 @@
</template>

<script lang="ts" setup>
import { Container } from "@/models/Container";
const { showSearch } = useSearchFilter();
const { enableActions } = config;
const clear = defineEmit();
const { container, streamConfig } = useContainerContext();
const { streamConfig } = useLoggingContext();
const { container } = defineProps<{ container: Container }>();
// container context is provided in the parent component: <LogContainer>
const { actionStates, start, stop, restart } = useContainerActions();
const { actionStates, start, stop, restart } = useContainerActions(toRef(() => container));
const downloadParams = computed(() =>
Object.entries(streamConfig)
Expand All @@ -119,7 +122,7 @@ const downloadParams = computed(() =>
const downloadUrl = computed(() =>
withBase(
`/api/hosts/${container.value.host}/containers/${container.value.id}/logs/download?${new URLSearchParams(downloadParams.value).toString()}`,
`/api/hosts/${container.host}/containers/${container.id}/logs/download?${new URLSearchParams(downloadParams.value).toString()}`,
),
);
Expand Down
54 changes: 54 additions & 0 deletions assets/components/ContainerViewer/ContainerLog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<template>
<ScrollableView :scrollable="scrollable" v-if="container">
<template #header v-if="showTitle">
<div class="mx-2 flex items-center gap-2 md:ml-4">
<ContainerTitle :container="container" />
<MultiContainerStat class="ml-auto" :containers="[container]" />

<ContainerActionsToolbar @clear="viewer?.clear()" class="mobile-hidden" :container="container" />
<a class="btn btn-circle btn-xs" @click="close()" v-if="closable">
<mdi:close />
</a>
</div>
</template>
<template #default="{ setLoading }">
<ViewerWithSource
ref="viewer"
@loading-more="setLoading($event)"
:stream-source="useContainerStream"
:entity="container"
:visible-keys="visibleKeys"
:show-container-name="false"
/>
</template>
</ScrollableView>
</template>
<script lang="ts" setup>
import ViewerWithSource from "@/components/LogViewer/ViewerWithSource.vue";
import { ComponentExposed } from "vue-component-type-helpers";
const {
id,
showTitle = false,
scrollable = false,
closable = false,
} = defineProps<{
id: string;
showTitle?: boolean;
scrollable?: boolean;
closable?: boolean;
}>();
const close = defineEmit();
const store = useContainerStore();
const container = store.currentContainer($$(id));
const visibleKeys = persistentVisibleKeysForContainer(container);
const viewer = ref<ComponentExposed<typeof ViewerWithSource>>();
provideContainerContext(container); // TODO remove
provideLoggingContext(toRef(() => [container.value]));
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,24 @@
{{ container.swarmId }}
</div>
</div>
<container-health :health="container.health" v-if="container.health"></container-health>
<tag class="mobile-hidden hidden font-mono @3xl:block" size="small">
<ContainerHealth :health="container.health" v-if="container.health" />
<Tag class="mobile-hidden hidden font-mono @3xl:block" size="small">
{{ container.image.replace(/@sha.*/, "") }}
</tag>
</Tag>
</div>
</template>

<script lang="ts" setup>
const { container } = useContainerContext();
import { Container } from "@/models/Container";
const { container } = defineProps<{ container: Container }>();
const pinned = computed({
get: () => pinnedContainers.value.has(container.value.name),
get: () => pinnedContainers.value.has(container.name),
set: (value) => {
if (value) {
pinnedContainers.value.add(container.value.name);
pinnedContainers.value.add(container.name);
} else {
pinnedContainers.value.delete(container.value.name);
pinnedContainers.value.delete(container.name);
}
},
});
Expand Down
9 changes: 5 additions & 4 deletions assets/components/FuzzySearchModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
</template>
<span data-name v-html="matchedName(result)"></span>
</div>
<distance-time :date="result.item.created" class="text-xs font-light" />
<DistanceTime :date="result.item.created" class="text-xs font-light" />
<a
@click.stop.prevent="addColumn(result.item)"
:title="$t('tooltip.pin-column')"
Expand Down Expand Up @@ -62,8 +62,9 @@ const input = ref<HTMLInputElement>();
const selectedIndex = ref(0);
const router = useRouter();
const store = useContainerStore();
const { containers } = storeToRefs(store);
const containerStore = useContainerStore();
const pinnedStore = usePinnedLogsStore();
const { containers } = storeToRefs(containerStore);
const list = computed(() => {
return containers.value.map(({ id, created, name, state, labels, hostLabel: host }) => {
Expand Down Expand Up @@ -121,7 +122,7 @@ function selected({ id }: { id: string }) {
close();
}
function addColumn(container: { id: string }) {
store.appendActiveContainer(container);
pinnedStore.pinContainer(container);
close();
}
Expand Down
46 changes: 46 additions & 0 deletions assets/components/GroupedViewer/GroupedLog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<template>
<ScrollableView :scrollable="scrollable" v-if="group.containers.length && ready">
<template #header>
<div class="mx-2 flex items-center gap-2 md:ml-4">
<div class="flex flex-1 gap-1.5 truncate @container md:gap-2">
<div class="inline-flex font-mono text-sm">
<div class="font-semibold">{{ group.containers.length }} containers</div>
</div>
</div>
<MultiContainerStat class="ml-auto" :containers="group.containers" />
</div>
</template>
<template #default="{ setLoading }">
<ViewerWithSource
ref="viewer"
@loading-more="setLoading($event)"
:stream-source="useGroupedStream"
:entity="group"
:visible-keys="visibleKeys"
:show-container-name="true"
/>
</template>
</ScrollableView>
</template>

<script lang="ts" setup>
import ViewerWithSource from "@/components/LogViewer/ViewerWithSource.vue";
import { GroupedContainers } from "@/models/Container";
const { name, scrollable = false } = defineProps<{
name: string;
scrollable?: boolean;
}>();
const containerStore = useContainerStore();
const { ready } = storeToRefs(containerStore);
const swarmStore = useSwarmStore();
const { customGroups } = storeToRefs(swarmStore);
const group = computed(() => customGroups.value.find((g) => g.name === name) ?? new GroupedContainers("", []));
const visibleKeys = ref<string[][]>([]);
provideLoggingContext(toRef(() => group.value.containers));
</script>
Loading

0 comments on commit ba0206a

Please sign in to comment.