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"
|
||||
placeholder="Search logs"
|
||||
class="h-12 !w-full !pl-10 !pr-48"
|
||||
:disabled="loading"
|
||||
@keydown.escape="clearSearch"
|
||||
/>
|
||||
<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">
|
||||
<XIcon class="h-5 w-5" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<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"
|
||||
>
|
||||
{{ pyroConsole.filteredOutput.value.length }}
|
||||
@ -29,11 +30,13 @@
|
||||
: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',
|
||||
{ 'scale-fullscreen screen-fixed inset-0 z-50 !rounded-none': isFullScreen },
|
||||
{ 'pointer-events-none': loading },
|
||||
]"
|
||||
:aria-hidden="loading"
|
||||
tabindex="-1"
|
||||
>
|
||||
<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"
|
||||
:style="`--transparency: ${Math.max(0, lerp(100, 0, bottomThreshold * 8))}%`"
|
||||
aria-hidden="true"
|
||||
@ -47,7 +50,7 @@
|
||||
/>
|
||||
</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"
|
||||
:style="
|
||||
bottomThreshold > 0
|
||||
@ -79,6 +82,7 @@
|
||||
</div>
|
||||
<div data-pyro-terminal-scroll-root class="relative h-full w-full">
|
||||
<div
|
||||
v-if="!loading"
|
||||
ref="scrollbarTrack"
|
||||
data-pyro-terminal-scrollbar-track
|
||||
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]"
|
||||
@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
|
||||
class="m-0 list-none p-0"
|
||||
data-pyro-terminal-virtual-list
|
||||
@ -205,6 +214,7 @@
|
||||
<slot />
|
||||
</div>
|
||||
<button
|
||||
v-if="!loading"
|
||||
data-pyro-fullscreen
|
||||
: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"
|
||||
@ -217,7 +227,7 @@
|
||||
|
||||
<Transition name="fade">
|
||||
<div
|
||||
v-if="hasSelection || isSingleLineSelected"
|
||||
v-if="(hasSelection || isSingleLineSelected) && !loading"
|
||||
class="absolute right-20 top-4 z-[3] flex flex-row items-center"
|
||||
:class="{ '!right-4': searchInput || hasSelection || isSingleLineSelected }"
|
||||
>
|
||||
@ -247,7 +257,7 @@
|
||||
|
||||
<Transition name="scroll-to-bottom">
|
||||
<button
|
||||
v-if="bottomThreshold > 0 && !isScrolledToBottom"
|
||||
v-if="bottomThreshold > 0 && !isScrolledToBottom && !loading"
|
||||
data-pyro-scrolltobottom
|
||||
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"
|
||||
@ -298,6 +308,7 @@ const cosmetics = $cosmetics;
|
||||
|
||||
const props = defineProps<{
|
||||
fullScreen: boolean;
|
||||
loading?: boolean;
|
||||
}>();
|
||||
|
||||
const BUFFER_SIZE = 5;
|
||||
@ -308,7 +319,7 @@ const SCROLL_END_DELAY = 150;
|
||||
const progressiveBlurIterations = ref(8);
|
||||
|
||||
const pyroConsole = usePyroConsole();
|
||||
const consoleOutput = pyroConsole.output;
|
||||
const consoleOutput = computed(() => (props.loading ? [] : pyroConsole.output.value));
|
||||
|
||||
const scrollContainer = ref<HTMLElement | null>(null);
|
||||
|
||||
|
@ -3,6 +3,8 @@
|
||||
data-pyro-server-stats
|
||||
style="font-variant-numeric: tabular-nums"
|
||||
class="flex select-none flex-col items-center gap-6 md:flex-row"
|
||||
:class="{ 'pointer-events-none': loading }"
|
||||
:aria-hidden="loading"
|
||||
>
|
||||
<div
|
||||
v-for="(metric, index) in metrics"
|
||||
@ -18,7 +20,7 @@
|
||||
<h3 class="flex items-center gap-2 text-base font-normal text-secondary">
|
||||
{{ metric.title }}
|
||||
<IssuesIcon
|
||||
v-if="metric.warning"
|
||||
v-if="metric.warning && !loading"
|
||||
v-tooltip="metric.warning"
|
||||
class="size-5"
|
||||
: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>
|
||||
|
||||
<component :is="metric.icon" class="absolute right-10 top-10 z-10" />
|
||||
<ClientOnly>
|
||||
<VueApexCharts
|
||||
v-if="metric.showGraph"
|
||||
type="area"
|
||||
height="142"
|
||||
:options="getChartOptions(metric.warning)"
|
||||
:series="[{ name: metric.title, data: metric.data }]"
|
||||
class="chart absolute bottom-0 left-0 right-0 w-full opacity-0"
|
||||
/>
|
||||
</ClientOnly>
|
||||
<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>
|
||||
<VueApexCharts
|
||||
v-if="metric.showGraph && !loading"
|
||||
type="area"
|
||||
height="142"
|
||||
:options="getChartOptions(metric.warning, index)"
|
||||
:series="[{ name: metric.title, data: metric.data }]"
|
||||
class="chart"
|
||||
:class="chartsReady.has(index) ? 'opacity-100' : 'opacity-0'"
|
||||
/>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NuxtLink
|
||||
:to="`/servers/manage/${serverId}/files`"
|
||||
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"
|
||||
<component
|
||||
:is="loading ? 'div' : 'NuxtLink'"
|
||||
: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">
|
||||
<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>
|
||||
</div>
|
||||
<h3 class="text-base font-normal text-secondary">Storage usage</h3>
|
||||
<FolderOpenIcon class="absolute right-10 top-10 size-8" />
|
||||
</NuxtLink>
|
||||
</component>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
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 type { Stats } from "~/types/servers";
|
||||
|
||||
@ -66,13 +78,28 @@ const route = useNativeRoute();
|
||||
const serverId = route.params.id;
|
||||
const VueApexCharts = defineAsyncComponent(() => import("vue3-apexcharts"));
|
||||
|
||||
const chartsReady = ref(new Set<number>());
|
||||
|
||||
const userPreferences = useStorage(`pyro-server-${serverId}-preferences`, {
|
||||
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 units = ["B", "KB", "MB", "GB"];
|
||||
@ -94,6 +121,29 @@ const updateGraphData = (arr: number[], newValue: number) => {
|
||||
};
|
||||
|
||||
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(
|
||||
(stats.value.ram_usage_bytes / stats.value.ram_total_bytes) * 100,
|
||||
100,
|
||||
@ -119,7 +169,7 @@ const metrics = computed(() => {
|
||||
? formatBytes(stats.value.ram_usage_bytes)
|
||||
: `${ramPercent.toFixed(2)}%`,
|
||||
max: userPreferences.value.ramAsNumber ? formatBytes(stats.value.ram_total_bytes) : "100%",
|
||||
icon: DBIcon,
|
||||
icon: DatabaseIcon,
|
||||
data: ramData.value,
|
||||
showGraph: true,
|
||||
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: {
|
||||
type: "area",
|
||||
animations: { enabled: false },
|
||||
@ -139,6 +189,10 @@ const getChartOptions = (hasWarning: string | null) => ({
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
events: {
|
||||
mounted: () => onChartReady(index),
|
||||
updated: () => onChartReady(index),
|
||||
},
|
||||
},
|
||||
stroke: { curve: "smooth", width: 3 },
|
||||
fill: {
|
||||
@ -172,24 +226,26 @@ const getChartOptions = (hasWarning: string | null) => ({
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.data.current,
|
||||
() => props.data?.current,
|
||||
(newStats) => {
|
||||
stats.value = newStats;
|
||||
if (newStats) {
|
||||
stats.value = newStats;
|
||||
}
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.chart {
|
||||
animation: fadeIn 0.2s ease-out 0.2s forwards;
|
||||
.chart-space {
|
||||
height: 142px;
|
||||
width: calc(100% + 48px);
|
||||
margin-left: -24px;
|
||||
margin-right: -24px;
|
||||
width: calc(100% + 48px) !important;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
.chart {
|
||||
width: 100% !important;
|
||||
height: 142px !important;
|
||||
transition: opacity 0.3s ease-out;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,11 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="isConnected && !isWsAuthIncorrect"
|
||||
class="relative flex select-none flex-col gap-6"
|
||||
data-pyro-server-manager-root
|
||||
>
|
||||
<div class="relative flex select-none flex-col gap-6" data-pyro-server-manager-root>
|
||||
<div
|
||||
v-if="inspectingError"
|
||||
v-if="inspectingError && isConnected && !isWsAuthIncorrect"
|
||||
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"
|
||||
>
|
||||
@ -77,26 +73,34 @@
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col-reverse gap-6 md:flex-col">
|
||||
<UiServersServerStats :data="stats" />
|
||||
<UiServersServerStats
|
||||
:data="isConnected && !isWsAuthIncorrect ? stats : undefined"
|
||||
:loading="!isConnected || isWsAuthIncorrect"
|
||||
/>
|
||||
|
||||
<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="{ 'border-0': !isConnected || isWsAuthIncorrect }"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-4">
|
||||
<h2 class="m-0 text-3xl font-extrabold text-contrast">Console</h2>
|
||||
|
||||
<UiServersPanelServerStatus :state="serverPowerState" />
|
||||
<UiServersPanelServerStatus
|
||||
v-if="isConnected && !isWsAuthIncorrect"
|
||||
:state="serverPowerState"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <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">
|
||||
|
||||
<UiServersPanelTerminal
|
||||
:full-screen="fullScreen"
|
||||
:loading="!isConnected || isWsAuthIncorrect"
|
||||
>
|
||||
<div class="relative w-full px-4 pt-4">
|
||||
<ul
|
||||
v-if="suggestions.length"
|
||||
v-if="suggestions.length && isConnected && !isWsAuthIncorrect"
|
||||
id="command-suggestions"
|
||||
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"
|
||||
@ -120,7 +124,7 @@
|
||||
</ul>
|
||||
<div class="relative flex items-center">
|
||||
<span
|
||||
v-if="bestSuggestion"
|
||||
v-if="bestSuggestion && isConnected && !isWsAuthIncorrect"
|
||||
class="pointer-events-none absolute left-[26px] transform select-none text-gray-400"
|
||||
>
|
||||
<span class="ml-[23.5px] whitespace-pre">{{
|
||||
@ -142,7 +146,7 @@
|
||||
<TerminalSquareIcon class="ml-3 h-5 w-5" />
|
||||
</div>
|
||||
<input
|
||||
v-if="isServerRunning"
|
||||
v-if="isServerRunning && isConnected && !isWsAuthIncorrect"
|
||||
v-model="commandInput"
|
||||
type="text"
|
||||
placeholder="Send a command"
|
||||
@ -168,21 +172,17 @@
|
||||
</UiServersPanelTerminal>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<UiServersOverviewLoading v-else-if="!isConnected && !isWsAuthIncorrect" />
|
||||
<div v-else-if="isWsAuthIncorrect" 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.
|
||||
(WebSocket Authentication Failed)
|
||||
</p>
|
||||
</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
|
||||
v-if="isWsAuthIncorrect"
|
||||
class="absolute inset-0 flex flex-col items-center justify-center bg-bg"
|
||||
>
|
||||
<h2>Could not connect to the server.</h2>
|
||||
<p>
|
||||
An error occurred while attempting to connect to your server. Please try refreshing the
|
||||
page. (WebSocket Authentication Failed)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</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 _CogIcon from './icons/cog.svg?component'
|
||||
import _CPUIcon from './icons/cpu.svg?component'
|
||||
import _DBIcon from './icons/db.svg?component'
|
||||
import _LoaderIcon from './icons/loader.svg?component'
|
||||
import _ImportIcon from './icons/import.svg?component'
|
||||
import _TimerIcon from './icons/timer.svg?component'
|
||||
@ -438,7 +437,6 @@ export const CubeIcon = _CubeIcon
|
||||
export const CloudIcon = _CloudIcon
|
||||
export const CogIcon = _CogIcon
|
||||
export const CPUIcon = _CPUIcon
|
||||
export const DBIcon = _DBIcon
|
||||
export const LoaderIcon = _LoaderIcon
|
||||
export const ImportIcon = _ImportIcon
|
||||
export const CardIcon = _CardIcon
|
||||
|
Loading…
x
Reference in New Issue
Block a user