mirror of
https://github.com/amir20/dozzle.git
synced 2025-12-25 14:59:26 +01:00
* WIP vue3 * WIP vue3 * WIP vue3 * Migrates to vitejs * Fixes js tests and removes not needed modules * Fixes unmount * Updates to use css instead for space * Fixes tests and rebases one more time * Uses orgua * Fixes migrations bugs with oruga and fixes scroll * Fixes v-deep * Fixes icons to prod * Fixes icons to prod * Adds favicon back * Transitions some to composition api * Updates another component to comp api * Cleans defineProps * Updates log messages * Moves more to compose api * Cleans up styles and rewrites event source * Tries to fix DOMPurify * Removes postcss * WIP typescript * Improves importing * Converts all to ts * Converts main to ts * Makes changes for tsconfig * Moves more to ts * Adds typing to store * More typing * Updates to ts * Updates the rest to ts * Fixes computes * Fixes unmount * Adds cypress with custom base fixed * Fixes jest tests * Fixes golang tests * Adds gitignore for cypress * Removes int in favor of e2e with cypress * Tries to fix int tests again * Adds title * Updates e2e tests * Uses vue for isMobile * Removes app spec * Cleans up docker * Adds drop down for settings * Fixes bug with restart * Fixes scroll up bug * Adds tests for light mode
159 lines
4.3 KiB
Vue
159 lines
4.3 KiB
Vue
<template>
|
|
<div>
|
|
<section class="hero is-small mt-4">
|
|
<div class="hero-body">
|
|
<div class="container">
|
|
<div class="columns">
|
|
<div class="column">
|
|
<h1 class="title">Hello, there!</h1>
|
|
</div>
|
|
<div class="column is-narrow" v-if="secured">
|
|
<a class="button is-primary is-small" :href="`${base}/logout`">Logout</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<section class="level section is-mobile">
|
|
<div class="level-item has-text-centered">
|
|
<div>
|
|
<p class="title">{{ containers.length }}</p>
|
|
<p class="heading">Total Containers</p>
|
|
</div>
|
|
</div>
|
|
<div class="level-item has-text-centered">
|
|
<div>
|
|
<p class="title">{{ runningContainers.length }}</p>
|
|
<p class="heading">Running</p>
|
|
</div>
|
|
</div>
|
|
<div class="level-item has-text-centered">
|
|
<div>
|
|
<p class="title">{{ version }}</p>
|
|
<p class="heading">Dozzle Version</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="columns is-centered section is-marginless">
|
|
<div class="column is-4">
|
|
<div class="panel">
|
|
<p class="panel-heading">Containers</p>
|
|
<div class="panel-block">
|
|
<p class="control has-icons-left">
|
|
<input
|
|
class="input"
|
|
type="text"
|
|
placeholder="Search Containers"
|
|
v-model="search"
|
|
@keyup.esc="search = null"
|
|
@keyup.enter="onEnter()"
|
|
/>
|
|
<span class="icon is-left">
|
|
<search-icon />
|
|
</span>
|
|
</p>
|
|
</div>
|
|
<p class="panel-tabs" v-if="!search">
|
|
<a :class="{ 'is-active': sort === 'running' }" @click="sort = 'running'">Running</a>
|
|
<a :class="{ 'is-active': sort === 'all' }" @click="sort = 'all'">All</a>
|
|
</p>
|
|
<router-link
|
|
:to="{ name: 'container', params: { id: item.id, name: item.name } }"
|
|
v-for="item in results.slice(0, 10)"
|
|
:key="item.id"
|
|
class="panel-block"
|
|
>
|
|
<span class="name">{{ item.name }}</span>
|
|
|
|
<div class="subtitle is-7 status">
|
|
<past-time :date="new Date(item.created * 1000)"></past-time>
|
|
</div>
|
|
</router-link>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { mapState } from "vuex";
|
|
import fuzzysort from "fuzzysort";
|
|
import SearchIcon from "~icons/mdi-light/magnify";
|
|
import PastTime from "../components/PastTime.vue";
|
|
import config from "../store/config";
|
|
|
|
export default {
|
|
name: "Index",
|
|
components: { SearchIcon, PastTime },
|
|
data() {
|
|
return {
|
|
version: config.version,
|
|
search: null,
|
|
sort: "running",
|
|
secured: config.secured,
|
|
base: config.base,
|
|
};
|
|
},
|
|
methods: {
|
|
onEnter() {
|
|
if (this.results.length == 1) {
|
|
const [item] = this.results;
|
|
this.$router.push({ name: "container", params: { id: item.id, name: item.name } });
|
|
}
|
|
},
|
|
},
|
|
computed: {
|
|
...mapState(["containers"]),
|
|
mostRecentContainers() {
|
|
return [...this.containers].sort((a, b) => b.created - a.created);
|
|
},
|
|
runningContainers() {
|
|
return this.mostRecentContainers.filter((c) => c.state === "running");
|
|
},
|
|
allContainers() {
|
|
return this.containers;
|
|
},
|
|
results() {
|
|
if (this.search) {
|
|
return fuzzysort.go(this.search, this.allContainers, { key: "name" }).map((i) => i.obj);
|
|
}
|
|
switch (this.sort) {
|
|
case "all":
|
|
return this.mostRecentContainers;
|
|
case "running":
|
|
return this.runningContainers;
|
|
|
|
default:
|
|
throw `Invalid sort order: ${this.sort}`;
|
|
}
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
<style lang="scss" scoped>
|
|
.panel {
|
|
border: 1px solid var(--border-color);
|
|
.panel-block,
|
|
.panel-tabs {
|
|
border-color: var(--border-color);
|
|
.is-active {
|
|
border-color: var(--border-hover-color);
|
|
}
|
|
.name {
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
}
|
|
.status {
|
|
margin-left: auto;
|
|
white-space: nowrap;
|
|
}
|
|
}
|
|
}
|
|
|
|
.icon {
|
|
padding: 10px 3px;
|
|
}
|
|
</style>
|