Merge branch 'amir20:master' into responsible

This commit is contained in:
Dmitry Mazurov
2022-11-03 22:48:44 +03:00
committed by GitHub
5 changed files with 3 additions and 478 deletions

View File

@@ -102,6 +102,7 @@ declare global {
const refThrottled: typeof import('@vueuse/core')['refThrottled']
const refWithControl: typeof import('@vueuse/core')['refWithControl']
const resolveComponent: typeof import('vue')['resolveComponent']
const resolveDirective: typeof import('vue')['resolveDirective']
const resolveRef: typeof import('@vueuse/core')['resolveRef']
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
const search: typeof import('./composables/settings')['search']
@@ -311,7 +312,7 @@ declare global {
}
// for vue template auto import
import { UnwrapRef } from 'vue'
declare module '@vue/runtime-core' {
declare module 'vue' {
interface ComponentCustomProperties {
readonly $$: UnwrapRef<typeof import('vue/macros')['$$']>
readonly $: UnwrapRef<typeof import('vue/macros')['$']>
@@ -414,6 +415,7 @@ declare module '@vue/runtime-core' {
readonly refThrottled: UnwrapRef<typeof import('@vueuse/core')['refThrottled']>
readonly refWithControl: UnwrapRef<typeof import('@vueuse/core')['refWithControl']>
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
readonly resolveDirective: UnwrapRef<typeof import('vue')['resolveDirective']>
readonly resolveRef: UnwrapRef<typeof import('@vueuse/core')['resolveRef']>
readonly resolveUnref: UnwrapRef<typeof import('@vueuse/core')['resolveUnref']>
readonly search: UnwrapRef<typeof import('./composables/settings')['search']>

View File

@@ -1,170 +0,0 @@
<template>
<div>
<section class="hero is-small mt-4">
<div class="hero-body">
<div class="container">
<div class="columns">
<div class="column is-narrow" v-if="secured">
<a class="button is-primary is-small" :href="`${base}/logout`">{{ $t("button.logout") }}</a>
</div>
</div>
</div>
</div>
</section>
<section class="level section">
<div class="level-item has-text-centered">
<div>
<p class="title">{{ containers.length }}</p>
<p class="heading">{{ $t("label.total-containers") }}</p>
</div>
</div>
<div class="level-item has-text-centered">
<div>
<p class="title">{{ runningContainers.length }}</p>
<p class="heading">{{ $t("label.running") }}</p>
</div>
</div>
<div class="level-item has-text-centered">
<div>
<p class="title" data-ci-skip>{{ totalCpu }}%</p>
<p class="heading">{{ $t("label.total-cpu-usage") }}</p>
</div>
</div>
<div class="level-item has-text-centered">
<div>
<p class="title" data-ci-skip>{{ formatBytes(totalMem) }}</p>
<p class="heading">{{ $t("label.total-mem-usage") }}</p>
</div>
</div>
<div class="level-item has-text-centered">
<div>
<p class="title">{{ version }}</p>
<p class="heading">{{ $t("label.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">{{ $t("label.containers") }}</p>
<div class="panel-block">
<p class="control has-icons-left">
<input
class="input"
type="text"
:placeholder="$t('placeholder.search-containers')"
v-model="query"
@keyup.esc="query = ''"
@keyup.enter="onEnter()"
/>
<span class="icon is-left">
<search-icon />
</span>
</p>
</div>
<p class="panel-tabs" v-if="query === ''">
<a :class="{ 'is-active': sort === 'running' }" @click="sort = 'running'">{{ $t("label.running") }}</a>
<a :class="{ 'is-active': sort === 'all' }" @click="sort = 'all'">{{ $t("label.all") }}</a>
</p>
<router-link
:to="{ name: 'container-id', params: { id: item.id } }"
v-for="item in data.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" setup>
import SearchIcon from "~icons/mdi-light/magnify";
import { useFuse } from "@vueuse/integrations/useFuse";
const { base, version, secured } = config;
const containerStore = useContainerStore();
const { containers } = storeToRefs(containerStore);
const router = useRouter();
const sort = $ref("running");
const query = ref("");
const mostRecentContainers = $computed(() => [...containers.value].sort((a, b) => b.created - a.created));
const runningContainers = $computed(() => mostRecentContainers.filter((c) => c.state === "running"));
const { results } = useFuse(query, containers, {
fuseOptions: { keys: ["name"] },
matchAllWhenSearchEmpty: false,
});
const data = computed(() => {
if (results.value.length) {
return results.value.map(({ item }) => item);
}
switch (sort) {
case "all":
return mostRecentContainers;
case "running":
return runningContainers;
default:
throw `Invalid sort order: ${sort}`;
}
});
let totalCpu = $ref(0);
useIntervalFn(
() => {
totalCpu = runningContainers.reduce((acc, c) => acc + (c.stat?.cpu ?? 0), 0);
},
1000,
{ immediate: true }
);
let totalMem = $ref(0);
useIntervalFn(
() => {
totalMem = runningContainers.reduce((acc, c) => acc + (c.stat?.memoryUsage ?? 0), 0);
},
1000,
{ immediate: true }
);
function onEnter() {
if (data.value.length > 0) {
const item = data.value[0];
router.push({ name: "container-id", params: { id: item.id } });
}
}
</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>

View File

@@ -1,79 +0,0 @@
<template>
<div class="hero is-halfheight">
<div class="hero-body">
<div class="container">
<section class="columns is-centered section">
<div class="column is-4">
<div class="card">
<div class="card-content">
<form action="" method="post" @submit.prevent="onLogin" ref="form">
<div class="field">
<label class="label">{{ $t("label.username") }}</label>
<div class="control">
<input
class="input"
type="text"
name="username"
autocomplete="username"
v-model="username"
autofocus
/>
</div>
</div>
<div class="field">
<label class="label">{{ $t("label.password") }}</label>
<div class="control">
<input
class="input"
type="password"
name="password"
autocomplete="current-password"
v-model="password"
/>
</div>
<p class="help is-danger" v-if="error">{{ $t("error.invalid-auth") }}</p>
</div>
<div class="field is-grouped is-grouped-centered mt-5">
<p class="control">
<button class="button is-primary" type="submit">{{ $t("button.login") }}</button>
</p>
</div>
</form>
</div>
</div>
</div>
</section>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
const { t } = useI18n();
setTitle(t("title.login"));
let error = $ref(false);
let username = $ref("");
let password = $ref("");
let form: HTMLFormElement = $ref();
async function onLogin() {
const response = await fetch(`${config.base}/api/validateCredentials`, {
body: new FormData(form),
method: "post",
});
if (response.status == 200) {
error = false;
window.location.href = `${config.base}/`;
} else {
error = true;
}
}
</script>
<route lang="yaml">
meta:
layout: splash
</route>

View File

@@ -1,203 +0,0 @@
<template>
<div>
<section class="section">
<div class="has-underline">
<h2 class="title is-4">{{ $t("settings.about") }}</h2>
</div>
<div>
<span v-html="$t('settings.using-version', { version: currentVersion })"></span>
<div
v-if="hasUpdate"
v-html="$t('settings.update-available', { nextVersion: nextRelease.name, href: nextRelease.html_url })"
></div>
</div>
</section>
<section class="section">
<div class="has-underline">
<h2 class="title is-4">{{ $t("settings.display") }}</h2>
</div>
<div class="item">
<o-switch v-model="smallerScrollbars"> {{ $t("settings.small-scrollbars") }} </o-switch>
</div>
<div class="item">
<o-switch v-model="showTimestamp"> {{ $t("settings.show-timesamps") }} </o-switch>
</div>
<div class="item">
<o-switch v-model="softWrap"> {{ $t("settings.soft-wrap") }}</o-switch>
</div>
<div class="item">
<div class="columns is-vcentered">
<div class="column is-narrow">
<o-field>
<o-dropdown v-model="hourStyle" aria-role="list">
<template #trigger>
<o-button variant="primary" type="button">
<span class="is-capitalized">{{ hourStyle }}</span>
<span class="icon">
<carbon-caret-down />
</span>
</o-button>
</template>
<o-dropdown-item :value="value" aria-role="listitem" v-for="value in ['auto', '12', '24']" :key="value">
<span class="is-capitalized">{{ value }}</span>
</o-dropdown-item>
</o-dropdown>
</o-field>
</div>
<div class="column">
{{ $t("settings.12-24-format") }}
</div>
</div>
</div>
<div class="item">
<div class="columns is-vcentered">
<div class="column is-narrow">
<o-field>
<o-dropdown v-model="size" aria-role="list">
<template #trigger>
<o-button variant="primary" type="button">
<span class="is-capitalized">{{ size }}</span>
<span class="icon">
<carbon-caret-down />
</span>
</o-button>
</template>
<o-dropdown-item
:value="value"
aria-role="listitem"
v-for="value in ['small', 'medium', 'large']"
:key="value"
>
<span class="is-capitalized">{{ value }}</span>
</o-dropdown-item>
</o-dropdown>
</o-field>
</div>
<div class="column">{{ $t("settings.font-size") }}</div>
</div>
</div>
<div class="item">
<div class="columns is-vcentered">
<div class="column is-narrow">
<o-field>
<o-dropdown v-model="lightTheme" aria-role="list">
<template #trigger>
<o-button variant="primary" type="button">
<span class="is-capitalized">{{ lightTheme }}</span>
<span class="icon">
<carbon-caret-down />
</span>
</o-button>
</template>
<o-dropdown-item
:value="value"
aria-role="listitem"
v-for="value in ['auto', 'dark', 'light']"
:key="value"
>
<span class="is-capitalized">{{ value }}</span>
</o-dropdown-item>
</o-dropdown>
</o-field>
</div>
<div class="column">{{ $t("settings.color-scheme") }}</div>
</div>
</div>
</section>
<section class="section">
<div class="has-underline">
<h2 class="title is-4">{{ $t("settings.options") }}</h2>
</div>
<div class="item">
<o-switch v-model="search">
<span v-html="$t('settings.search')"></span>
</o-switch>
</div>
<div class="item">
<o-switch v-model="showAllContainers"> {{ $t("settings.show-stopped-containers") }} </o-switch>
</div>
</section>
</div>
</template>
<script lang="ts" setup>
import gt from "semver/functions/gt";
import {
search,
lightTheme,
smallerScrollbars,
showTimestamp,
hourStyle,
showAllContainers,
size,
softWrap,
} from "@/composables/settings";
const { t } = useI18n();
setTitle(t("title.settings"));
const currentVersion = $ref(config.version);
let nextRelease = $ref({ html_url: "", name: "" });
let hasUpdate = $ref(false);
async function fetchNextRelease() {
if (!["dev", "master"].includes(currentVersion)) {
const response = await fetch("https://api.github.com/repos/amir20/dozzle/releases/latest");
if (response.ok) {
const release = await response.json();
hasUpdate = gt(release.tag_name, currentVersion);
nextRelease = release;
}
} else {
hasUpdate = true;
nextRelease = {
html_url: "",
name: "master",
};
}
}
fetchNextRelease();
</script>
<style lang="scss" scoped>
.title {
color: var(--title-color);
}
a.next-release {
text-decoration: underline;
&:hover {
text-decoration: none;
}
}
.section {
padding: 1rem 1.5rem;
}
.has-underline {
border-bottom: 1px solid var(--border-color);
padding: 1em 0px;
margin-bottom: 1em;
}
.item {
padding: 1em 0;
}
code {
border-radius: 4px;
background-color: #444;
}
</style>

View File

@@ -1,25 +0,0 @@
<script lang="ts" setup>
const router = useRouter();
const route = useRoute();
const store = useContainerStore();
const { visibleContainers } = storeToRefs(store);
watch(visibleContainers, (newValue) => {
if (newValue) {
if (route.query.name) {
const [container, _] = visibleContainers.value.filter((c) => c.name == route.query.name);
if (container) {
router.push({ name: "container-id", params: { id: container.id } });
} else {
console.error(`No containers found matching name=${route.query.name}. Redirecting to /`);
router.push({ name: "index" });
}
} else {
console.error(`Expection query parameter name to be set. Redirecting to /`);
router.push({ name: "index" });
}
}
});
</script>
<template></template>