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:
parent
742c0edd9e
commit
6ec1dcf088
@ -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;
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
68
apps/frontend/src/store/console.ts
Normal file
68
apps/frontend/src/store/console.ts
Normal 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,
|
||||||
|
};
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user