fix: hydration issues caused by duplicate components on servers panel (#3753)
* fix: server stats icons * fix: fix chart jumping * refactor: iconComponent -> icon * fix: panel hydration issues * fix: apply requested changes
This commit is contained in:
parent
a3839461cf
commit
f8fb23e05f
@ -1,80 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
aria-hidden="true"
|
|
||||||
style="font-variant-numeric: tabular-nums"
|
|
||||||
class="pointer-events-none h-full w-full select-none"
|
|
||||||
>
|
|
||||||
<div class="flex flex-col gap-6">
|
|
||||||
<div class="flex flex-row items-center gap-6">
|
|
||||||
<div
|
|
||||||
class="relative max-h-[156px] min-h-[156px] w-full overflow-hidden rounded-2xl bg-bg-raised p-8"
|
|
||||||
>
|
|
||||||
<div class="relative z-10 -ml-3 w-fit rounded-xl px-3 py-1">
|
|
||||||
<div class="-mb-0.5 mt-0.5 flex flex-row items-center gap-2">
|
|
||||||
<h2 class="m-0 -ml-0.5 text-3xl font-extrabold text-contrast">0.00%</h2>
|
|
||||||
<h3 class="relative z-10 text-sm font-normal text-secondary">/ 100%</h3>
|
|
||||||
</div>
|
|
||||||
<h3 class="relative z-10 text-base font-normal text-secondary">CPU usage</h3>
|
|
||||||
</div>
|
|
||||||
<CPUIcon class="absolute right-10 top-10" />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="relative max-h-[156px] min-h-[156px] w-full overflow-hidden rounded-2xl bg-bg-raised p-8"
|
|
||||||
>
|
|
||||||
<div class="relative z-10 -ml-3 w-fit rounded-xl px-3 py-1">
|
|
||||||
<div class="-mb-0.5 mt-0.5 flex flex-row items-center gap-2">
|
|
||||||
<h2 class="m-0 -ml-0.5 text-3xl font-extrabold text-contrast">0.00%</h2>
|
|
||||||
<h3 class="relative z-10 text-sm font-normal text-secondary">/ 100%</h3>
|
|
||||||
</div>
|
|
||||||
<h3 class="relative z-10 text-base font-normal text-secondary">Memory usage</h3>
|
|
||||||
</div>
|
|
||||||
<DBIcon class="absolute right-10 top-10" />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="relative isolate min-h-[156px] w-full overflow-hidden rounded-2xl bg-bg-raised p-8 transition-transform duration-100 hover:scale-105 active:scale-100"
|
|
||||||
>
|
|
||||||
<div class="flex flex-row items-center gap-2">
|
|
||||||
<h2 class="m-0 -ml-0.5 mt-1 text-3xl font-extrabold text-contrast">0 B</h2>
|
|
||||||
</div>
|
|
||||||
<h3 class="relative z-10 text-base font-normal text-secondary">Storage usage</h3>
|
|
||||||
<FolderOpenIcon class="absolute right-10 top-10 size-8" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="relative flex h-full w-full flex-col gap-3 overflow-hidden rounded-2xl bg-bg-raised p-8"
|
|
||||||
>
|
|
||||||
<div class="experimental-styles-within flex flex-row items-center">
|
|
||||||
<div class="flex flex-row items-center gap-4">
|
|
||||||
<h2 class="m-0 text-3xl font-extrabold text-contrast">Console</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="relative w-full">
|
|
||||||
<input type="text" placeholder="Search logs" class="h-12 !w-full !pl-10 !pr-48" />
|
|
||||||
<SearchIcon class="absolute left-4 top-1/2 -translate-y-1/2" />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="console relative h-full min-h-[516px] w-full overflow-hidden rounded-xl bg-bg text-sm"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { CPUIcon, DBIcon, FolderOpenIcon, SearchIcon } from "@modrinth/assets";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
html.light-mode .console {
|
|
||||||
background: var(--color-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark-mode .console {
|
|
||||||
background: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.oled-mode .console {
|
|
||||||
background: black;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -7,16 +7,17 @@
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder="Search logs"
|
placeholder="Search logs"
|
||||||
class="h-12 !w-full !pl-10 !pr-48"
|
class="h-12 !w-full !pl-10 !pr-48"
|
||||||
|
:disabled="loading"
|
||||||
@keydown.escape="clearSearch"
|
@keydown.escape="clearSearch"
|
||||||
/>
|
/>
|
||||||
<SearchIcon class="absolute left-4 top-1/2 -translate-y-1/2" />
|
<SearchIcon class="absolute left-4 top-1/2 -translate-y-1/2" />
|
||||||
<ButtonStyled v-if="searchInput" @click="clearSearch">
|
<ButtonStyled v-if="searchInput && !loading" @click="clearSearch">
|
||||||
<button class="absolute right-2 top-1/2 -translate-y-1/2">
|
<button class="absolute right-2 top-1/2 -translate-y-1/2">
|
||||||
<XIcon class="h-5 w-5" />
|
<XIcon class="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
<span
|
<span
|
||||||
v-if="pyroConsole.filteredOutput.value.length && searchInput"
|
v-if="pyroConsole.filteredOutput.value.length && searchInput && !loading"
|
||||||
class="pointer-events-none absolute right-12 top-1/2 -translate-y-1/2 select-none whitespace-pre text-sm"
|
class="pointer-events-none absolute right-12 top-1/2 -translate-y-1/2 select-none whitespace-pre text-sm"
|
||||||
>
|
>
|
||||||
{{ pyroConsole.filteredOutput.value.length }}
|
{{ pyroConsole.filteredOutput.value.length }}
|
||||||
@ -29,11 +30,13 @@
|
|||||||
:class="[
|
:class="[
|
||||||
'terminal-font console relative z-[1] flex h-full w-full flex-col items-center justify-between overflow-hidden rounded-t-xl px-1 text-sm transition-transform duration-300',
|
'terminal-font console relative z-[1] flex h-full w-full flex-col items-center justify-between overflow-hidden rounded-t-xl px-1 text-sm transition-transform duration-300',
|
||||||
{ 'scale-fullscreen screen-fixed inset-0 z-50 !rounded-none': isFullScreen },
|
{ 'scale-fullscreen screen-fixed inset-0 z-50 !rounded-none': isFullScreen },
|
||||||
|
{ 'pointer-events-none': loading },
|
||||||
]"
|
]"
|
||||||
|
:aria-hidden="loading"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="cosmetics.advancedRendering"
|
v-if="cosmetics.advancedRendering && !loading"
|
||||||
class="progressive-gradient pointer-events-none absolute -bottom-6 left-0 z-[2] h-[10rem] w-full overflow-hidden rounded-xl"
|
class="progressive-gradient pointer-events-none absolute -bottom-6 left-0 z-[2] h-[10rem] w-full overflow-hidden rounded-xl"
|
||||||
:style="`--transparency: ${Math.max(0, lerp(100, 0, bottomThreshold * 8))}%`"
|
:style="`--transparency: ${Math.max(0, lerp(100, 0, bottomThreshold * 8))}%`"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@ -47,7 +50,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else-if="!loading"
|
||||||
class="pointer-events-none absolute bottom-0 left-0 right-0 z-[2] h-[196px] w-full"
|
class="pointer-events-none absolute bottom-0 left-0 right-0 z-[2] h-[196px] w-full"
|
||||||
:style="
|
:style="
|
||||||
bottomThreshold > 0
|
bottomThreshold > 0
|
||||||
@ -79,6 +82,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div data-pyro-terminal-scroll-root class="relative h-full w-full">
|
<div data-pyro-terminal-scroll-root class="relative h-full w-full">
|
||||||
<div
|
<div
|
||||||
|
v-if="!loading"
|
||||||
ref="scrollbarTrack"
|
ref="scrollbarTrack"
|
||||||
data-pyro-terminal-scrollbar-track
|
data-pyro-terminal-scrollbar-track
|
||||||
class="absolute -right-1 bottom-16 top-4 z-[4] w-4 overflow-hidden"
|
class="absolute -right-1 bottom-16 top-4 z-[4] w-4 overflow-hidden"
|
||||||
@ -118,7 +122,12 @@
|
|||||||
class="scrollbar-none absolute left-0 top-0 h-full w-full select-text overflow-x-auto overflow-y-auto py-6 pb-[72px]"
|
class="scrollbar-none absolute left-0 top-0 h-full w-full select-text overflow-x-auto overflow-y-auto py-6 pb-[72px]"
|
||||||
@scroll.passive="() => handleListScroll()"
|
@scroll.passive="() => handleListScroll()"
|
||||||
>
|
>
|
||||||
<div data-pyro-terminal-virtual-height-watcher :style="{ height: `${totalHeight}px` }">
|
<div v-if="loading" class="h-full w-full" />
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
data-pyro-terminal-virtual-height-watcher
|
||||||
|
:style="{ height: `${totalHeight}px` }"
|
||||||
|
>
|
||||||
<ul
|
<ul
|
||||||
class="m-0 list-none p-0"
|
class="m-0 list-none p-0"
|
||||||
data-pyro-terminal-virtual-list
|
data-pyro-terminal-virtual-list
|
||||||
@ -205,6 +214,7 @@
|
|||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
v-if="!loading"
|
||||||
data-pyro-fullscreen
|
data-pyro-fullscreen
|
||||||
:label="isFullScreen ? 'Exit full screen' : 'Enter full screen'"
|
:label="isFullScreen ? 'Exit full screen' : 'Enter full screen'"
|
||||||
class="experimental-styles-within absolute right-4 top-4 z-[3] grid h-12 w-12 place-content-center rounded-full border-[1px] border-solid border-button-border bg-bg-raised text-contrast transition-all duration-200 hover:scale-110 active:scale-95"
|
class="experimental-styles-within absolute right-4 top-4 z-[3] grid h-12 w-12 place-content-center rounded-full border-[1px] border-solid border-button-border bg-bg-raised text-contrast transition-all duration-200 hover:scale-110 active:scale-95"
|
||||||
@ -217,7 +227,7 @@
|
|||||||
|
|
||||||
<Transition name="fade">
|
<Transition name="fade">
|
||||||
<div
|
<div
|
||||||
v-if="hasSelection || isSingleLineSelected"
|
v-if="(hasSelection || isSingleLineSelected) && !loading"
|
||||||
class="absolute right-20 top-4 z-[3] flex flex-row items-center"
|
class="absolute right-20 top-4 z-[3] flex flex-row items-center"
|
||||||
:class="{ '!right-4': searchInput || hasSelection || isSingleLineSelected }"
|
:class="{ '!right-4': searchInput || hasSelection || isSingleLineSelected }"
|
||||||
>
|
>
|
||||||
@ -247,7 +257,7 @@
|
|||||||
|
|
||||||
<Transition name="scroll-to-bottom">
|
<Transition name="scroll-to-bottom">
|
||||||
<button
|
<button
|
||||||
v-if="bottomThreshold > 0 && !isScrolledToBottom"
|
v-if="bottomThreshold > 0 && !isScrolledToBottom && !loading"
|
||||||
data-pyro-scrolltobottom
|
data-pyro-scrolltobottom
|
||||||
label="Scroll to bottom"
|
label="Scroll to bottom"
|
||||||
class="scroll-to-bottom-btn experimental-styles-within absolute bottom-[4.5rem] right-4 z-[3] grid h-12 w-12 place-content-center rounded-full border-[1px] border-solid border-button-border bg-bg-raised text-contrast transition-all duration-200 hover:scale-110 active:scale-95"
|
class="scroll-to-bottom-btn experimental-styles-within absolute bottom-[4.5rem] right-4 z-[3] grid h-12 w-12 place-content-center rounded-full border-[1px] border-solid border-button-border bg-bg-raised text-contrast transition-all duration-200 hover:scale-110 active:scale-95"
|
||||||
@ -298,6 +308,7 @@ const cosmetics = $cosmetics;
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
fullScreen: boolean;
|
fullScreen: boolean;
|
||||||
|
loading?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const BUFFER_SIZE = 5;
|
const BUFFER_SIZE = 5;
|
||||||
@ -308,7 +319,7 @@ const SCROLL_END_DELAY = 150;
|
|||||||
const progressiveBlurIterations = ref(8);
|
const progressiveBlurIterations = ref(8);
|
||||||
|
|
||||||
const pyroConsole = usePyroConsole();
|
const pyroConsole = usePyroConsole();
|
||||||
const consoleOutput = pyroConsole.output;
|
const consoleOutput = computed(() => (props.loading ? [] : pyroConsole.output.value));
|
||||||
|
|
||||||
const scrollContainer = ref<HTMLElement | null>(null);
|
const scrollContainer = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
data-pyro-server-stats
|
data-pyro-server-stats
|
||||||
style="font-variant-numeric: tabular-nums"
|
style="font-variant-numeric: tabular-nums"
|
||||||
class="flex select-none flex-col items-center gap-6 md:flex-row"
|
class="flex select-none flex-col items-center gap-6 md:flex-row"
|
||||||
|
:class="{ 'pointer-events-none': loading }"
|
||||||
|
:aria-hidden="loading"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(metric, index) in metrics"
|
v-for="(metric, index) in metrics"
|
||||||
@ -18,7 +20,7 @@
|
|||||||
<h3 class="flex items-center gap-2 text-base font-normal text-secondary">
|
<h3 class="flex items-center gap-2 text-base font-normal text-secondary">
|
||||||
{{ metric.title }}
|
{{ metric.title }}
|
||||||
<IssuesIcon
|
<IssuesIcon
|
||||||
v-if="metric.warning"
|
v-if="metric.warning && !loading"
|
||||||
v-tooltip="metric.warning"
|
v-tooltip="metric.warning"
|
||||||
class="size-5"
|
class="size-5"
|
||||||
:style="{ color: 'var(--color-orange)' }"
|
:style="{ color: 'var(--color-orange)' }"
|
||||||
@ -28,37 +30,47 @@
|
|||||||
<div class="absolute -left-8 -top-4 h-28 w-56 rounded-full bg-bg-raised blur-lg" />
|
<div class="absolute -left-8 -top-4 h-28 w-56 rounded-full bg-bg-raised blur-lg" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<component :is="metric.icon" class="absolute right-10 top-10 z-10" />
|
<component
|
||||||
|
:is="metric.icon"
|
||||||
|
class="absolute right-10 top-10 z-10 size-8"
|
||||||
|
style="width: 2rem; height: 2rem"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="chart-space absolute bottom-0 left-0 right-0">
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<VueApexCharts
|
<VueApexCharts
|
||||||
v-if="metric.showGraph"
|
v-if="metric.showGraph && !loading"
|
||||||
type="area"
|
type="area"
|
||||||
height="142"
|
height="142"
|
||||||
:options="getChartOptions(metric.warning)"
|
:options="getChartOptions(metric.warning, index)"
|
||||||
:series="[{ name: metric.title, data: metric.data }]"
|
:series="[{ name: metric.title, data: metric.data }]"
|
||||||
class="chart absolute bottom-0 left-0 right-0 w-full opacity-0"
|
class="chart"
|
||||||
|
:class="chartsReady.has(index) ? 'opacity-100' : 'opacity-0'"
|
||||||
/>
|
/>
|
||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<NuxtLink
|
<component
|
||||||
:to="`/servers/manage/${serverId}/files`"
|
:is="loading ? 'div' : 'NuxtLink'"
|
||||||
class="relative isolate min-h-[156px] w-full overflow-hidden rounded-2xl bg-bg-raised p-8 transition-transform duration-100 hover:scale-105 active:scale-100"
|
:to="loading ? undefined : `/servers/manage/${serverId}/files`"
|
||||||
|
class="relative isolate min-h-[156px] w-full overflow-hidden rounded-2xl bg-bg-raised p-8"
|
||||||
|
:class="loading ? '' : 'transition-transform duration-100 hover:scale-105 active:scale-100'"
|
||||||
>
|
>
|
||||||
<div class="flex flex-row items-center gap-2">
|
<div class="flex flex-row items-center gap-2">
|
||||||
<h2 class="m-0 -ml-0.5 mt-1 text-3xl font-extrabold text-contrast">
|
<h2 class="m-0 -ml-0.5 mt-1 text-3xl font-extrabold text-contrast">
|
||||||
{{ formatBytes(stats.storage_usage_bytes) }}
|
{{ loading ? "0 B" : formatBytes(stats.storage_usage_bytes) }}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-base font-normal text-secondary">Storage usage</h3>
|
<h3 class="text-base font-normal text-secondary">Storage usage</h3>
|
||||||
<FolderOpenIcon class="absolute right-10 top-10 size-8" />
|
<FolderOpenIcon class="absolute right-10 top-10 size-8" />
|
||||||
</NuxtLink>
|
</component>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, shallowRef } from "vue";
|
import { ref, computed, shallowRef } from "vue";
|
||||||
import { FolderOpenIcon, CPUIcon, DBIcon, IssuesIcon } from "@modrinth/assets";
|
import { FolderOpenIcon, CPUIcon, DatabaseIcon, IssuesIcon } from "@modrinth/assets";
|
||||||
import { useStorage } from "@vueuse/core";
|
import { useStorage } from "@vueuse/core";
|
||||||
import type { Stats } from "~/types/servers";
|
import type { Stats } from "~/types/servers";
|
||||||
|
|
||||||
@ -66,13 +78,28 @@ const route = useNativeRoute();
|
|||||||
const serverId = route.params.id;
|
const serverId = route.params.id;
|
||||||
const VueApexCharts = defineAsyncComponent(() => import("vue3-apexcharts"));
|
const VueApexCharts = defineAsyncComponent(() => import("vue3-apexcharts"));
|
||||||
|
|
||||||
|
const chartsReady = ref(new Set<number>());
|
||||||
|
|
||||||
const userPreferences = useStorage(`pyro-server-${serverId}-preferences`, {
|
const userPreferences = useStorage(`pyro-server-${serverId}-preferences`, {
|
||||||
ramAsNumber: false,
|
ramAsNumber: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const props = defineProps<{ data: Stats }>();
|
const props = withDefaults(defineProps<{ data?: Stats; loading?: boolean }>(), {
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
|
|
||||||
const stats = shallowRef(props.data.current);
|
const stats = shallowRef(
|
||||||
|
props.data?.current || {
|
||||||
|
cpu_percent: 0,
|
||||||
|
ram_usage_bytes: 0,
|
||||||
|
ram_total_bytes: 1, // Avoid division by zero
|
||||||
|
storage_usage_bytes: 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const onChartReady = (index: number) => {
|
||||||
|
chartsReady.value.add(index);
|
||||||
|
};
|
||||||
|
|
||||||
const formatBytes = (bytes: number) => {
|
const formatBytes = (bytes: number) => {
|
||||||
const units = ["B", "KB", "MB", "GB"];
|
const units = ["B", "KB", "MB", "GB"];
|
||||||
@ -94,6 +121,29 @@ const updateGraphData = (arr: number[], newValue: number) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const metrics = computed(() => {
|
const metrics = computed(() => {
|
||||||
|
if (props.loading) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: "CPU usage",
|
||||||
|
value: "0.00%",
|
||||||
|
max: "100%",
|
||||||
|
icon: CPUIcon,
|
||||||
|
data: cpuData.value,
|
||||||
|
showGraph: false,
|
||||||
|
warning: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Memory usage",
|
||||||
|
value: "0.00%",
|
||||||
|
max: "100%",
|
||||||
|
icon: DatabaseIcon,
|
||||||
|
data: ramData.value,
|
||||||
|
showGraph: false,
|
||||||
|
warning: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
const ramPercent = Math.min(
|
const ramPercent = Math.min(
|
||||||
(stats.value.ram_usage_bytes / stats.value.ram_total_bytes) * 100,
|
(stats.value.ram_usage_bytes / stats.value.ram_total_bytes) * 100,
|
||||||
100,
|
100,
|
||||||
@ -119,7 +169,7 @@ const metrics = computed(() => {
|
|||||||
? formatBytes(stats.value.ram_usage_bytes)
|
? formatBytes(stats.value.ram_usage_bytes)
|
||||||
: `${ramPercent.toFixed(2)}%`,
|
: `${ramPercent.toFixed(2)}%`,
|
||||||
max: userPreferences.value.ramAsNumber ? formatBytes(stats.value.ram_total_bytes) : "100%",
|
max: userPreferences.value.ramAsNumber ? formatBytes(stats.value.ram_total_bytes) : "100%",
|
||||||
icon: DBIcon,
|
icon: DatabaseIcon,
|
||||||
data: ramData.value,
|
data: ramData.value,
|
||||||
showGraph: true,
|
showGraph: true,
|
||||||
warning: ramPercent >= 90 ? "Memory usage is very high" : null,
|
warning: ramPercent >= 90 ? "Memory usage is very high" : null,
|
||||||
@ -127,7 +177,7 @@ const metrics = computed(() => {
|
|||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
const getChartOptions = (hasWarning: string | null) => ({
|
const getChartOptions = (hasWarning: string | null, index: number) => ({
|
||||||
chart: {
|
chart: {
|
||||||
type: "area",
|
type: "area",
|
||||||
animations: { enabled: false },
|
animations: { enabled: false },
|
||||||
@ -139,6 +189,10 @@ const getChartOptions = (hasWarning: string | null) => ({
|
|||||||
top: 0,
|
top: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
},
|
},
|
||||||
|
events: {
|
||||||
|
mounted: () => onChartReady(index),
|
||||||
|
updated: () => onChartReady(index),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
stroke: { curve: "smooth", width: 3 },
|
stroke: { curve: "smooth", width: 3 },
|
||||||
fill: {
|
fill: {
|
||||||
@ -172,24 +226,26 @@ const getChartOptions = (hasWarning: string | null) => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.data.current,
|
() => props.data?.current,
|
||||||
(newStats) => {
|
(newStats) => {
|
||||||
|
if (newStats) {
|
||||||
stats.value = newStats;
|
stats.value = newStats;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.chart {
|
.chart-space {
|
||||||
animation: fadeIn 0.2s ease-out 0.2s forwards;
|
height: 142px;
|
||||||
|
width: calc(100% + 48px);
|
||||||
margin-left: -24px;
|
margin-left: -24px;
|
||||||
margin-right: -24px;
|
margin-right: -24px;
|
||||||
width: calc(100% + 48px) !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeIn {
|
.chart {
|
||||||
to {
|
width: 100% !important;
|
||||||
opacity: 1;
|
height: 142px !important;
|
||||||
}
|
transition: opacity 0.3s ease-out;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div class="relative flex select-none flex-col gap-6" data-pyro-server-manager-root>
|
||||||
<div
|
<div
|
||||||
v-if="isConnected && !isWsAuthIncorrect"
|
v-if="inspectingError && isConnected && !isWsAuthIncorrect"
|
||||||
class="relative flex select-none flex-col gap-6"
|
|
||||||
data-pyro-server-manager-root
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-if="inspectingError"
|
|
||||||
data-pyro-servers-inspecting-error
|
data-pyro-servers-inspecting-error
|
||||||
class="flex justify-between rounded-2xl border-2 border-solid border-red bg-bg-red p-4 font-semibold text-contrast"
|
class="flex justify-between rounded-2xl border-2 border-solid border-red bg-bg-red p-4 font-semibold text-contrast"
|
||||||
>
|
>
|
||||||
@ -77,26 +73,34 @@
|
|||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col-reverse gap-6 md:flex-col">
|
<div class="flex flex-col-reverse gap-6 md:flex-col">
|
||||||
<UiServersServerStats :data="stats" />
|
<UiServersServerStats
|
||||||
|
:data="isConnected && !isWsAuthIncorrect ? stats : undefined"
|
||||||
|
:loading="!isConnected || isWsAuthIncorrect"
|
||||||
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="relative flex h-[700px] w-full flex-col gap-3 overflow-hidden rounded-2xl border border-divider bg-bg-raised p-4 transition-all duration-300 ease-in-out md:p-8"
|
class="relative flex h-[700px] w-full flex-col gap-3 overflow-hidden rounded-2xl border border-divider bg-bg-raised p-4 transition-all duration-300 ease-in-out md:p-8"
|
||||||
|
:class="{ 'border-0': !isConnected || isWsAuthIncorrect }"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<h2 class="m-0 text-3xl font-extrabold text-contrast">Console</h2>
|
<h2 class="m-0 text-3xl font-extrabold text-contrast">Console</h2>
|
||||||
|
<UiServersPanelServerStatus
|
||||||
|
v-if="isConnected && !isWsAuthIncorrect"
|
||||||
|
:state="serverPowerState"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<UiServersPanelServerStatus :state="serverPowerState" />
|
<UiServersPanelTerminal
|
||||||
</div>
|
:full-screen="fullScreen"
|
||||||
</div>
|
:loading="!isConnected || isWsAuthIncorrect"
|
||||||
<!-- <div class="flex flex-row items-center gap-2 text-sm font-medium">
|
>
|
||||||
<InfoIcon class="hidden sm:block" />
|
|
||||||
Click and drag to select lines, then CMD+C to copy
|
|
||||||
</div> -->
|
|
||||||
<UiServersPanelTerminal :full-screen="fullScreen">
|
|
||||||
<div class="relative w-full px-4 pt-4">
|
<div class="relative w-full px-4 pt-4">
|
||||||
<ul
|
<ul
|
||||||
v-if="suggestions.length"
|
v-if="suggestions.length && isConnected && !isWsAuthIncorrect"
|
||||||
id="command-suggestions"
|
id="command-suggestions"
|
||||||
ref="suggestionsList"
|
ref="suggestionsList"
|
||||||
class="mt-1 max-h-60 w-full list-none overflow-auto rounded-md border border-divider bg-bg-raised p-0 shadow-lg"
|
class="mt-1 max-h-60 w-full list-none overflow-auto rounded-md border border-divider bg-bg-raised p-0 shadow-lg"
|
||||||
@ -120,7 +124,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<div class="relative flex items-center">
|
<div class="relative flex items-center">
|
||||||
<span
|
<span
|
||||||
v-if="bestSuggestion"
|
v-if="bestSuggestion && isConnected && !isWsAuthIncorrect"
|
||||||
class="pointer-events-none absolute left-[26px] transform select-none text-gray-400"
|
class="pointer-events-none absolute left-[26px] transform select-none text-gray-400"
|
||||||
>
|
>
|
||||||
<span class="ml-[23.5px] whitespace-pre">{{
|
<span class="ml-[23.5px] whitespace-pre">{{
|
||||||
@ -142,7 +146,7 @@
|
|||||||
<TerminalSquareIcon class="ml-3 h-5 w-5" />
|
<TerminalSquareIcon class="ml-3 h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
v-if="isServerRunning"
|
v-if="isServerRunning && isConnected && !isWsAuthIncorrect"
|
||||||
v-model="commandInput"
|
v-model="commandInput"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Send a command"
|
placeholder="Send a command"
|
||||||
@ -168,21 +172,17 @@
|
|||||||
</UiServersPanelTerminal>
|
</UiServersPanelTerminal>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<UiServersOverviewLoading v-else-if="!isConnected && !isWsAuthIncorrect" />
|
<div
|
||||||
<div v-else-if="isWsAuthIncorrect" class="flex flex-col">
|
v-if="isWsAuthIncorrect"
|
||||||
|
class="absolute inset-0 flex flex-col items-center justify-center bg-bg"
|
||||||
|
>
|
||||||
<h2>Could not connect to the server.</h2>
|
<h2>Could not connect to the server.</h2>
|
||||||
<p>
|
<p>
|
||||||
An error occurred while attempting to connect to your server. Please try refreshing the page.
|
An error occurred while attempting to connect to your server. Please try refreshing the
|
||||||
(WebSocket Authentication Failed)
|
page. (WebSocket Authentication Failed)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="flex flex-col">
|
|
||||||
<h2>Could not connect to the server.</h2>
|
|
||||||
<p>
|
|
||||||
An error occurred while attempting to connect to your server. Please try refreshing the page.
|
|
||||||
(No further information)
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -1 +1,18 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-cpu-icon lucide-cpu"><path d="M12 20v2"/><path d="M12 2v2"/><path d="M17 20v2"/><path d="M17 2v2"/><path d="M2 12h2"/><path d="M2 17h2"/><path d="M2 7h2"/><path d="M20 12h2"/><path d="M20 17h2"/><path d="M20 7h2"/><path d="M7 20v2"/><path d="M7 2v2"/><rect x="4" y="4" width="16" height="16" rx="2"/><rect x="8" y="8" width="8" height="8" rx="1"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||||
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
class="lucide lucide-cpu-icon lucide-cpu">
|
||||||
|
<path d="M12 20v2" />
|
||||||
|
<path d="M12 2v2" />
|
||||||
|
<path d="M17 20v2" />
|
||||||
|
<path d="M17 2v2" />
|
||||||
|
<path d="M2 12h2" />
|
||||||
|
<path d="M2 17h2" />
|
||||||
|
<path d="M2 7h2" />
|
||||||
|
<path d="M20 12h2" />
|
||||||
|
<path d="M20 17h2" />
|
||||||
|
<path d="M20 7h2" />
|
||||||
|
<path d="M7 20v2" />
|
||||||
|
<path d="M7 2v2" />
|
||||||
|
<rect x="4" y="4" width="16" height="16" rx="2" />
|
||||||
|
<rect x="8" y="8" width="8" height="8" rx="1" />
|
||||||
|
</svg>
|
Before Width: | Height: | Size: 555 B After Width: | Height: | Size: 648 B |
@ -1,14 +0,0 @@
|
|||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
class="absolute right-8 top-8 size-8"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 633 B |
@ -207,7 +207,6 @@ import _CubeIcon from './icons/cube.svg?component'
|
|||||||
import _CloudIcon from './icons/cloud.svg?component'
|
import _CloudIcon from './icons/cloud.svg?component'
|
||||||
import _CogIcon from './icons/cog.svg?component'
|
import _CogIcon from './icons/cog.svg?component'
|
||||||
import _CPUIcon from './icons/cpu.svg?component'
|
import _CPUIcon from './icons/cpu.svg?component'
|
||||||
import _DBIcon from './icons/db.svg?component'
|
|
||||||
import _LoaderIcon from './icons/loader.svg?component'
|
import _LoaderIcon from './icons/loader.svg?component'
|
||||||
import _ImportIcon from './icons/import.svg?component'
|
import _ImportIcon from './icons/import.svg?component'
|
||||||
import _TimerIcon from './icons/timer.svg?component'
|
import _TimerIcon from './icons/timer.svg?component'
|
||||||
@ -438,7 +437,6 @@ export const CubeIcon = _CubeIcon
|
|||||||
export const CloudIcon = _CloudIcon
|
export const CloudIcon = _CloudIcon
|
||||||
export const CogIcon = _CogIcon
|
export const CogIcon = _CogIcon
|
||||||
export const CPUIcon = _CPUIcon
|
export const CPUIcon = _CPUIcon
|
||||||
export const DBIcon = _DBIcon
|
|
||||||
export const LoaderIcon = _LoaderIcon
|
export const LoaderIcon = _LoaderIcon
|
||||||
export const ImportIcon = _ImportIcon
|
export const ImportIcon = _ImportIcon
|
||||||
export const CardIcon = _CardIcon
|
export const CardIcon = _CardIcon
|
||||||
|
Loading…
x
Reference in New Issue
Block a user