1
0
mirror of https://github.com/amir20/dozzle.git synced 2025-12-21 13:23:07 +01:00
Files
dozzle/assets/components/SideMenu.vue
Amir Raminfar 9f3a256334 feat!: refactors UI using faster components and clean up visually (#2381)
* feat: moves to tailwindcss and better component library

* update styles

* creates toggle component

* adds drop down component

* cleans up components

* removes unused components

* uses tailwind for scroll view

* removes table component

* improves animation

* cleans up more styles

* uses more tailwind

* cleans up more styles with flex

* more styles

* removes bulma

* adds colors

* updates modules

* fixes bugs

* stops importing styles.scss

* more clean up

* cleans up headers

* cleans up title

* fixes title

* fixes mobile-hidden

* fixes shadow

* fixes colors

* add tailwindcss/nesting

* adds more colors

* fixes more colors

* updates colors

* fixes colors

* colors

* fixes menu on left

* menu and modal

* menu and modal

* fuzzy search

* fixes menu on left

* remove logs

* cleans up search

* adds host to search

* remove outline from inputs

* cleans up left search icon

* removes unused styles

* fixes docker

* removes sass!

* cleans up styles

* Fixe smobile menu

* fixes mobile menu

* fixes typecheck

* fixes seconday color

* adds drop down for container

* cleans header css

* updates css

* fixes other layouts

* updates some tests

* fixes border

* fixes home screen font

* fixes top header

* fixes tests

* fixes fieldlist

* fixes complex

* cleans up more

* removes index

* fixes tests

* fixes tests

* resolves conflicts
2023-09-22 10:59:29 -07:00

207 lines
5.2 KiB
Vue

<template>
<div v-if="ready" data-testid="side-menu">
<div class="breadcrumbs text-sm">
<ul>
<li><a @click.prevent="setHost(null)">Hosts</a></li>
<li v-if="sessionHost && hosts[sessionHost]">
{{ hosts[sessionHost].name }}
</li>
</ul>
</div>
<transition :name="sessionHost ? 'slide-left' : 'slide-right'" mode="out-in">
<ul class="menu p-0" v-if="!sessionHost">
<li v-for="host in config.hosts">
<a @click.prevent="setHost(host.id)">
<ph:computer-tower />
{{ host.name }}
</a>
</li>
</ul>
<transition-group tag="ul" name="list" class="containers menu p-0 [&_li.menu-title]:px-0" v-else>
<li
v-for="item in menuItems"
:key="item.id"
:class="isLabel(item) ? 'menu-title' : item.state"
:data-testid="item.id"
>
<template v-if="isLabel(item)">
{{ item.label }}
</template>
<popup v-else>
<router-link
:to="{ name: 'container-id', params: { id: item.id } }"
active-class="active-primary"
:title="item.name"
>
<div class="truncate">
{{ item.name }}<span class="font-light opacity-70" v-if="item.isSwarm">{{ item.swarmId }}</span>
</div>
<span
class="pin"
@click.stop.prevent="store.appendActiveContainer(item)"
v-show="!activeContainersById[item.id]"
:title="$t('tooltip.pin-column')"
>
<cil:columns />
</span>
<container-health :health="item.health"></container-health>
</router-link>
<template #content>
<container-popup :container="item"></container-popup>
</template>
</popup>
</li>
</transition-group>
</transition>
</div>
<div role="status" class="flex animate-pulse flex-col gap-4" v-else>
<div class="h-3 w-full rounded-full bg-base-content/50 opacity-50" v-for="_ in 9"></div>
<span class="sr-only">Loading...</span>
</div>
</template>
<script lang="ts" setup>
import { Container } from "@/models/Container";
import { sessionHost } from "@/composables/storage";
const { t } = useI18n();
const store = useContainerStore();
const { activeContainers, visibleContainers, ready } = storeToRefs(store);
function setHost(host: string | null) {
sessionHost.value = host;
}
const debouncedIds = debouncedRef(pinnedContainers, 200);
const sortedContainers = computed(() =>
visibleContainers.value
.filter((c) => c.host === sessionHost.value)
.sort((a, b) => {
if (a.state === "running" && b.state !== "running") {
return -1;
} else if (a.state !== "running" && b.state === "running") {
return 1;
} else {
return a.name.localeCompare(b.name);
}
}),
);
const groupedContainers = computed(() =>
sortedContainers.value.reduce(
(acc, item) => {
if (debouncedIds.value.has(item.storageKey)) {
acc.pinned.push(item);
} else {
acc.unpinned.push(item);
}
return acc;
},
{ pinned: [] as Container[], unpinned: [] as Container[] },
),
);
type MenuLabel = { label: string; id: string; state: string };
const pinnedLabel = { label: t("label.pinned"), id: "pinned", state: "label" } as MenuLabel;
const allLabel = { label: t("label.containers"), id: "containers", state: "label" } as MenuLabel;
function isLabel(item: Container | MenuLabel): item is MenuLabel {
return (item as MenuLabel).label !== undefined;
}
const menuItems = computed(() => {
if (groupedContainers.value.pinned.length > 0) {
return [pinnedLabel, ...groupedContainers.value.pinned, allLabel, ...groupedContainers.value.unpinned];
} else {
return [allLabel, ...groupedContainers.value.unpinned];
}
});
const hosts = computed(() =>
config.hosts.reduce(
(acc, item) => {
acc[item.id] = item;
return acc;
},
{} as Record<string, { name: string; id: string }>,
),
);
const activeContainersById = computed(() =>
activeContainers.value.reduce(
(acc, item) => {
acc[item.id] = item;
return acc;
},
{} as Record<string, Container>,
),
);
</script>
<style scoped lang="postcss">
.containers a {
@apply auto-cols-[auto_max-content];
.pin {
display: none;
&:hover {
@apply text-secondary;
}
}
&:hover {
.pin {
display: inline-block;
}
}
}
li.exited {
@apply opacity-50;
}
.slide-left-enter-active,
.slide-left-leave-active,
.slide-right-enter-active,
.slide-right-leave-active {
transition: all 0.1s ease-out;
}
.slide-left-enter-from {
opacity: 0;
transform: translateX(20px);
}
.slide-right-enter-from {
opacity: 0;
transform: translateX(-20px);
}
.slide-left-leave-to {
opacity: 0;
transform: translateX(-20px);
}
.slide-right-leave-to {
opacity: 0;
transform: translateX(20px);
}
.list-move,
.list-enter-active,
.list-leave-active {
transition: all 0.19s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
.list-leave-active {
position: absolute;
}
</style>