Servers console performance improvements (#3007)

* feat: init selecting paper+purpur on purchase flow

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: properly implement Paper/Purpur in Platform

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: correct wording

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: redo platform modal

Signed-off-by: Evan Song <theevansong@gmail.com>

* Switch to HCaptcha for Auth-related captchas (#2945)

* Switch to HCaptcha for Auth-related captchas

* run fmt

* fix hcaptcha not loading

* fix: more robust loader dropdown logic

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: handle "not yet supported" install err

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: fix icon kerfuffles

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: improve vanilla install modal title

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: spacing

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: usePyroConsole store instead of passing a prop to prevent bulk panel refreshing

* chore: improve no loader state

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: type error

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust mod version modal title

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust modpack warning copy

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: vanilla empty state in content page

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust copy

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: update icon

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: loader type

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: loader type

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: always show dropdown if possible

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: improve spacing

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: appear disabled

Signed-off-by: Evan Song <theevansong@gmail.com>

* h

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: if reinstalling, show it on the modal title

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: put it in the dropdown, they said

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust style

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: sort paper-purpur versions desc

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: do not consider backup limit in reinstall prompt

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: backup locking, plugin support

* fix: content type error

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: casing

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: plugins pt 2

* feat: backups, mrpack

* fix: type errors come on

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: spacing

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: type maxing

* chore: show copy button on allocation rows

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: suspend improvement

---------

Signed-off-by: Evan Song <theevansong@gmail.com>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Jai A <jaiagr+gpg@pm.me>
Co-authored-by: TheWander02 <48934424+thewander02@users.noreply.github.com>
This commit is contained in:
Evan Song 2024-12-11 16:49:22 -07:00 committed by GitHub
parent 742c0edd9e
commit 6ec1dcf088
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 89 additions and 26 deletions

View File

@ -136,15 +136,18 @@
<script setup lang="ts"> <script setup lang="ts">
import { RightArrowIcon } from "@modrinth/assets"; import { RightArrowIcon } from "@modrinth/assets";
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from "vue"; import { ref, computed, onMounted, onUnmounted, watch, nextTick } from "vue";
import { usePyroConsole } from "~/store/console.ts";
const { $cosmetics } = useNuxtApp(); const { $cosmetics } = useNuxtApp();
const cosmetics = $cosmetics; const cosmetics = $cosmetics;
const props = defineProps<{ const props = defineProps<{
consoleOutput: string[];
fullScreen: boolean; fullScreen: boolean;
}>(); }>();
const pyroConsole = usePyroConsole();
const consoleOutput = pyroConsole.output;
const scrollContainer = ref<HTMLElement | null>(null); const scrollContainer = ref<HTMLElement | null>(null);
const itemHeights = ref<number[]>([]); const itemHeights = ref<number[]>([]);
const averageItemHeight = ref(36); const averageItemHeight = ref(36);
@ -170,7 +173,7 @@ const handleScrollEvent = () => {
const totalHeight = computed( const totalHeight = computed(
() => () =>
itemHeights.value.reduce((sum, height) => sum + height, 0) || itemHeights.value.reduce((sum, height) => sum + height, 0) ||
props.consoleOutput.length * averageItemHeight.value, consoleOutput.value.length * averageItemHeight.value,
); );
watch(totalHeight, () => { watch(totalHeight, () => {
@ -223,7 +226,7 @@ const visibleStartIndex = computed(() => {
let index = 0; let index = 0;
let offset = 0; let offset = 0;
while ( while (
index < props.consoleOutput.length && index < consoleOutput.value.length &&
offset < scrollTop.value - bufferSize * averageItemHeight.value offset < scrollTop.value - bufferSize * averageItemHeight.value
) { ) {
offset += itemHeights.value[index] || averageItemHeight.value; offset += itemHeights.value[index] || averageItemHeight.value;
@ -236,17 +239,17 @@ const visibleEndIndex = computed(() => {
let index = visibleStartIndex.value; let index = visibleStartIndex.value;
let offset = getItemOffset(index); let offset = getItemOffset(index);
while ( while (
index < props.consoleOutput.length && index < consoleOutput.value.length &&
offset < scrollTop.value + clientHeight.value + bufferSize * averageItemHeight.value offset < scrollTop.value + clientHeight.value + bufferSize * averageItemHeight.value
) { ) {
offset += itemHeights.value[index] || averageItemHeight.value; offset += itemHeights.value[index] || averageItemHeight.value;
index++; index++;
} }
return Math.min(props.consoleOutput.length - 1, index); return Math.min(consoleOutput.value.length - 1, index);
}); });
const visibleItems = computed(() => const visibleItems = computed(() =>
props.consoleOutput.slice(visibleStartIndex.value, visibleEndIndex.value + 1), consoleOutput.value.slice(visibleStartIndex.value, visibleEndIndex.value + 1),
); );
const offsetY = computed(() => getItemOffset(visibleStartIndex.value)); const offsetY = computed(() => getItemOffset(visibleStartIndex.value));
@ -280,7 +283,7 @@ const updateItemHeights = async () => {
const index = visibleStartIndex.value + idx; const index = visibleStartIndex.value + idx;
const height = el.getBoundingClientRect().height; const height = el.getBoundingClientRect().height;
itemHeights.value[index] = height; itemHeights.value[index] = height;
const content = props.consoleOutput[index]; const content = consoleOutput.value[index];
if (content) { if (content) {
cachedHeights.value.set(content, height); cachedHeights.value.set(content, height);
} }
@ -457,7 +460,7 @@ const initializeTerminal = async () => {
updateClientHeight(); updateClientHeight();
const initialHeights = props.consoleOutput.map( const initialHeights = consoleOutput.value.map(
(content) => cachedHeights.value.get(content) || averageItemHeight.value, (content) => cachedHeights.value.get(content) || averageItemHeight.value,
); );
itemHeights.value = initialHeights; itemHeights.value = initialHeights;
@ -487,7 +490,7 @@ onUnmounted(() => {
}); });
watch( watch(
() => props.consoleOutput, () => consoleOutput.value,
async (newOutput) => { async (newOutput) => {
const newItemsCount = newOutput.length - itemHeights.value.length; const newItemsCount = newOutput.length - itemHeights.value.length;

View File

@ -276,7 +276,6 @@
:stats="stats" :stats="stats"
:server-power-state="serverPowerState" :server-power-state="serverPowerState"
:power-state-details="powerStateDetails" :power-state-details="powerStateDetails"
:console-output="throttledConsoleOutput"
:socket="socket" :socket="socket"
:server="server" :server="server"
@reinstall="onReinstall" @reinstall="onReinstall"
@ -302,9 +301,9 @@ import {
} from "@modrinth/assets"; } from "@modrinth/assets";
import DOMPurify from "dompurify"; import DOMPurify from "dompurify";
import { ButtonStyled } from "@modrinth/ui"; import { ButtonStyled } from "@modrinth/ui";
import { refThrottled } from "@vueuse/core";
import { Intercom, shutdown } from "@intercom/messenger-js-sdk"; import { Intercom, shutdown } from "@intercom/messenger-js-sdk";
import type { ServerState, Stats, WSEvent, WSInstallationResultEvent } from "~/types/servers"; import type { ServerState, Stats, WSEvent, WSInstallationResultEvent } from "~/types/servers";
import { usePyroConsole } from "~/store/console.ts";
const socket = ref<WebSocket | null>(null); const socket = ref<WebSocket | null>(null);
const isReconnecting = ref(false); const isReconnecting = ref(false);
@ -357,9 +356,8 @@ const serverData = computed(() => server.general);
const error = ref<Error | null>(null); const error = ref<Error | null>(null);
const isConnected = ref(false); const isConnected = ref(false);
const isWSAuthIncorrect = ref(false); const isWSAuthIncorrect = ref(false);
const maxConsoleOutput = 5000; const pyroConsole = usePyroConsole();
const consoleOutput = ref<string[]>([]); console.log("||||||||||||||||||||||| console", pyroConsole.output);
const throttledConsoleOutput = refThrottled(consoleOutput, 200);
const cpuData = ref<number[]>([]); const cpuData = ref<number[]>([]);
const ramData = ref<number[]>([]); const ramData = ref<number[]>([]);
const isActioning = ref(false); const isActioning = ref(false);
@ -439,7 +437,7 @@ const connectWebSocket = () => {
return; return;
} }
consoleOutput.value = []; pyroConsole.clear();
socket.value?.send(JSON.stringify({ event: "auth", jwt: wsAuth.value?.token })); socket.value?.send(JSON.stringify({ event: "auth", jwt: wsAuth.value?.token }));
isConnected.value = true; isConnected.value = true;
isReconnecting.value = false; isReconnecting.value = false;
@ -447,7 +445,7 @@ const connectWebSocket = () => {
if (firstConnect.value) { if (firstConnect.value) {
for (let i = 0; i < initialConsoleMessage.length; i++) { for (let i = 0; i < initialConsoleMessage.length; i++) {
consoleOutput.value.push(initialConsoleMessage[i]); pyroConsole.addLine(initialConsoleMessage[i]);
} }
} }
@ -470,9 +468,7 @@ const connectWebSocket = () => {
socket.value.onclose = () => { socket.value.onclose = () => {
if (isMounted.value) { if (isMounted.value) {
consoleOutput.value.push( pyroConsole.addLine("\nSomething went wrong with the connection, we're reconnecting...");
"\nSomething went wrong with the connection, we're reconnecting...",
);
isConnected.value = false; isConnected.value = false;
scheduleReconnect(); scheduleReconnect();
} }
@ -530,10 +526,7 @@ const handleWebSocketMessage = (data: WSEvent) => {
case "log": case "log":
// eslint-disable-next-line no-case-declarations // eslint-disable-next-line no-case-declarations
const log = data.message.split("\n").filter((l) => l.trim()); const log = data.message.split("\n").filter((l) => l.trim());
if (consoleOutput.value.length > maxConsoleOutput) { pyroConsole.addLines(log);
consoleOutput.value.shift();
}
consoleOutput.value.push(...log);
break; break;
case "stats": case "stats":
updateStats(data); updateStats(data);
@ -623,7 +616,7 @@ const onReinstall = (potentialArgs: any) => {
// serverData.value.loader_version = potentialArgs.lVersion; // serverData.value.loader_version = potentialArgs.lVersion;
// serverData.value.mc_version = potentialArgs.mVersion; // serverData.value.mc_version = potentialArgs.mVersion;
// if (potentialArgs?.loader) { // if (potentialArgs?.loader) {
// console.log("setting loader to", potentialArgs.loader); // console.log("setting loadeconsole
// serverData.value.loader = potentialArgs.loader; // serverData.value.loader = potentialArgs.loader;
// } // }
// if (potentialArgs?.lVersion) { // if (potentialArgs?.lVersion) {

View File

@ -88,7 +88,7 @@
<UiServersPanelServerStatus :state="serverPowerState" /> <UiServersPanelServerStatus :state="serverPowerState" />
</div> </div>
</div> </div>
<UiServersPanelTerminal :console-output="consoleOutput" :full-screen="fullScreen"> <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"
@ -192,7 +192,6 @@ type ServerProps = {
isConnected: boolean; isConnected: boolean;
isWsAuthIncorrect: boolean; isWsAuthIncorrect: boolean;
stats: Stats; stats: Stats;
consoleOutput: string[];
serverPowerState: ServerState; serverPowerState: ServerState;
powerStateDetails?: { powerStateDetails?: {
oom_killed?: boolean; oom_killed?: boolean;

View File

@ -0,0 +1,68 @@
import { createGlobalState } from "@vueuse/core";
import { type Ref, ref } from "vue";
/**
* Maximum number of console output lines to store
* @type {number}
*/
const maxLines = 5000;
/**
* Provides a global console output state management system
* Allows adding, storing, and clearing console output with a maximum line limit
*
* @returns {Object} Console state management methods and reactive state
* @property {Ref<string[]>} consoleOutput - Reactive array of console output lines
* @property {function(string): void} addConsoleOutput - Method to add a new console output line
* @property {function(): void} clear - Method to clear all console output
*/
export const usePyroConsole = createGlobalState(() => {
/**
* Reactive array storing console output lines
* @type {Ref<string[]>}
*/
const output: Ref<string[]> = ref<string[]>([]);
/**
* Adds a new output line to the console output
* Automatically removes the oldest line if max output is exceeded
*
* @param {string} line - The console output line to add
*/
const addLine = (line: string): void => {
output.value.push(line);
if (output.value.length > maxLines) {
output.value.shift();
}
};
/**
* Adds multiple output lines to the console output
* Automatically removes the oldest lines if max output is exceeded
*
* @param {string[]} lines - The console output lines to add
* @returns {void}
*/
const addLines = (lines: string[]): void => {
output.value.push(...lines);
if (output.value.length > maxLines) {
output.value.splice(0, output.value.length - maxLines);
}
};
/**
* Clears all console output lines
*/
const clear = (): void => {
output.value = [];
};
return {
output,
addLine,
addLines,
clear,
};
});